This is an R Markdown Notebook. When you execute code within the notebook, the results appear beneath the code.

Try executing this chunk by clicking the Run button within the chunk or by placing your cursor inside it and pressing Ctrl+Shift+Enter.

source("tianfengRwrappers.R")
library(xgboost)
library(Matrix)
library(mclust)
library(tidyverse)
ds2 <- readRDS("ds2.rds")
Idents(ds2) <- ds2$seurat_clusters
Idents(ds1) <- ds1$seurat_clusters
Idents(ds0) <- ds0$seurat_clusters

数值化

ds2训练分类器

ds2_data <- get_data_table(ds2, highvar = F, type = "data")
ds2_label <- as.numeric(as.character(Idents(ds2)))

index <- c(1:dim(ds2_data)[2]) %>% sample(ceiling(0.3*dim(ds2_data)[2]), replace = F, prob = NULL)
colnames(ds2_data) <- NULL

ds2_train_data <- list(data = t(as(ds2_data[,-index],"dgCMatrix")), label = ds2_label[-index])
ds2_test_data <- list(data = t(as(ds2_data[,index],"dgCMatrix")), label = ds2_label[index])

ds2_train <- xgb.DMatrix(data = ds2_train_data$data,label = ds2_train_data$label)
ds2_test <- xgb.DMatrix(data = ds2_test_data$data,label = ds2_test_data$label)

watchlist <- list(train = ds2_train, eval = ds2_test)
xgb_param <- list(eta = 0.2, max_depth = 6, 
                  subsample = 0.6,  num_class = length(table(Idents(ds2))),
                  objective = "multi:softprob", eval_metric = 'mlogloss')

bst_model <- xgb.train(xgb_param, ds2_train, nrounds = 100, watchlist, verbose = 0)
saveRDS(bst_model, "ds2_model.rds")
eval_loss <- bst_model[["evaluation_log"]][["eval_mlogloss"]]
plot_ly(data.frame(eval_loss), x = c(1:100), y = eval_loss) %>% 
  add_trace(type = "scatter", mode = "markers+lines", 
            marker = list(color = "black", line = list(color = "#1E90FFC7", width = 1)),
            line = list(color = "#1E90FF80", width = 2)) %>% 
  layout(xaxis = list(title = "epoch"),yaxis = list(title = "eval_mlogloss"))
importance <- xgb.importance(colnames(ds2_train), model = bst_model)
head(importance)
xgb.ggplot.importance(head(importance,20),n_clusters = 1) + theme_bw()+theme(
    axis.title.x = element_text(size = 15), axis.text.x = element_text(size = 12, colour = "black"),
    axis.title.y = element_text(size = 15), axis.text.y = element_text(size = 12, colour = "black"),
    legend.text = element_text(size = 20), legend.title = element_blank(), panel.grid = element_blank())

ds2 -> ds1

ARI = 0.1695417

Idents(ds1) <- ds1$seurat_clusters
temp <- get_data_table(ds1, highvar = F, type = "data")
ds1_data <- matrix(data=0, nrow = length(rownames(ds2_data)), ncol = length(colnames(temp)), 
                   byrow = FALSE, dimnames = list(rownames(ds2_data),colnames(temp)))
for(i in intersect(rownames(ds2_data), rownames(temp))){
  ds1_data[i,] <- temp[i,]
}
rm(temp)
ds1_label <- as.numeric(as.character(Idents(ds1)))
colnames(ds1_data) <- NULL
ds1_test_data <- list(data = t(as(ds1_data,"dgCMatrix")), label = ds1_label)
ds1_test <- xgb.DMatrix(data = ds1_test_data$data,label = ds1_test_data$label)

#预测结果
predict_ds1_test <- predict(bst_model, newdata = ds1_test)

predict_prop_ds1 <- matrix(data=predict_ds1_test, nrow = length(levels(Idents(ds2))), 
                           ncol = ncol(ds1), byrow = FALSE, 
                           dimnames = list(levels(Idents(ds2)),colnames(ds1)))

## 得到分群结果
ds1_res <- apply(predict_prop_ds1,2,func,rownames(predict_prop_ds1))
adjustedRandIndex(ds1_res, ds1_test_data$label)

Idents(ds1) <- factor(ds1_res,levels = c(0:4))
umapplot(ds1)
ds1$supclustering <- Idents(ds1) #保存监督聚类结果

数值化地投射回umap

embedding <- FetchData(object = ds1, vars = c("UMAP_1", "UMAP_2"))
embedding <- cbind(embedding, t(predict_prop_ds1))

ggobj <- ggplot() +
  geom_point(data = embedding[embedding$`0`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `0`), shape=16, size = 3, alpha=0.5) + 
  scale_color_gradient('0', low = "#FFFFFF00", high = "#6dc0a6") +
  new_scale("color") +
    geom_point(data = embedding[embedding$`1`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `1`),shape=16, size = 3, alpha=0.5) + 
  scale_color_gradient('1', low = "#FFFFFF00", high = "#e2b398") +
   new_scale("color") +
    geom_point(data = embedding[embedding$`2`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `2`),shape=16, size = 3, alpha=0.5) + 
  scale_color_gradient('2', low = "#FFFFFF00", high = "#e2a2ca") +
  new_scale("color") +
    geom_point(data = embedding[embedding$`3`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `3`),shape=16, size = 3, alpha=0.5) + 
  scale_color_gradient('3', low = "#FFFFFF00", high = "#d1eba8") +
   new_scale("color") +
      geom_point(data = embedding[embedding$`4`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `4`),shape=16, size = 3, alpha=0.5) + 
  scale_color_gradient('4', low = "#FFFFFF00", high = "#b1d6fb") +
    new_scale("color") +
        xlab("UMAP 1") + ylab("UMAP 2")  +
        theme(axis.line = element_line(arrow = arrow(length = unit(0.2, "cm")))) +
        scale_y_continuous(breaks = NULL) +
        scale_x_continuous(breaks = NULL) + 
  theme(panel.background = element_blank(), panel.grid = element_blank(), legend.position = "bottom")
ggsave("pre_ds1_umap.svg",device = svg,plot = ggobj,height = 10,width = 10)

ds2 -> ds0

ARI = 0.6664657

Idents(ds0) <- ds0$seurat_clusters
temp <- get_data_table(ds0, highvar = F, type = "data")
ds0_data <- matrix(data=0, nrow = length(rownames(ds2_data)), ncol = length(colnames(temp)), 
                   byrow = FALSE, dimnames = list(rownames(ds2_data),colnames(temp)))
for(i in intersect(rownames(ds2_data), rownames(temp))){
  ds0_data[i,] <- temp[i,]
}
rm(temp)
ds0_label <- as.numeric(as.character(Idents(ds0)))
colnames(ds0_data) <- NULL
ds0_test_data <- list(data = t(as(ds0_data,"dgCMatrix")), label = ds0_label)
ds0_test <- xgb.DMatrix(data = ds0_test_data$data,label = ds0_test_data$label)

#预测结果

predict_ds0_test <- predict(bst_model, newdata = ds0_test)

predict_prop_ds0 <- matrix(data=predict_ds0_test, nrow = length(levels(Idents(ds2))), 
                           ncol = ncol(ds0), byrow = FALSE, 
                           dimnames = list(levels(Idents(ds2)),colnames(ds0)))

## 得到分群结果
ds0_res <- apply(predict_prop_ds0,2,func,rownames(predict_prop_ds0))
adjustedRandIndex(ds0_res, ds0_test_data$label)
Idents(ds0) <- factor(ds0_res,levels = c(0:4))
umapplot(ds0)
ds0$supclustering <- Idents(ds0) #保存监督聚类结果
embedding <- FetchData(object = ds0, vars = c("UMAP_1", "UMAP_2"))
embedding <- cbind(embedding, t(predict_prop_ds0))

ggobj <- ggplot() +
  geom_point(data = embedding[embedding$`0`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `0`), shape=16, size = 3, alpha=0.5) + 
  scale_color_gradient('0', low = "#FFFFFF00", high = "#6dc0a6") +
  new_scale("color") +
    geom_point(data = embedding[embedding$`1`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `1`),shape=16, size = 3, alpha=0.5) + 
  scale_color_gradient('1', low = "#FFFFFF00", high = "#e2b398") +
   new_scale("color") +
    geom_point(data = embedding[embedding$`2`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `2`),shape=16, size = 3, alpha=0.5) + 
  scale_color_gradient('2', low = "#FFFFFF00", high = "#e2a2ca") +
  new_scale("color") +
    geom_point(data = embedding[embedding$`3`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `3`),shape=16, size = 3, alpha=0.5) + 
  scale_color_gradient('3', low = "#FFFFFF00", high = "#d1eba8") +
   new_scale("color") +
      geom_point(data = embedding[embedding$`4`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `4`),shape=16, size = 3, alpha=0.5) + 
  scale_color_gradient('4', low = "#FFFFFF00", high = "#b1d6fb") +
    new_scale("color") +
        xlab("UMAP 1") + ylab("UMAP 2")  +
        theme(axis.line = element_line(arrow = arrow(length = unit(0.2, "cm")))) +
        scale_y_continuous(breaks = NULL) +
        scale_x_continuous(breaks = NULL) + 
  theme(panel.background = element_blank(), panel.grid = element_blank(), legend.position = "bottom")
ggsave("pre_ds0_umap.svg",device = svg,plot = ggobj,height = 10,width = 10)

PA -> AC

Idents(ds2_PA) <- ds2_PA$seurat_clusters
selected_features <- read.csv("./datatable/selected_features.csv", stringsAsFactors = F)
selected_features <- selected_features$x
PA_data <- get_data_table(ds2_PA, highvar = F, type = "data")
PA_data <- PA_data[selected_features,]
PA_label <- as.numeric(as.character(Idents(ds2_PA)))
colnames(PA_data) <- NULL

PA_train_data <- list(data = t(as(PA_data,"dgCMatrix")), label = PA_label)
PA_train <- xgb.DMatrix(data = PA_train_data$data,label = PA_train_data$label)
xgb_param <- list(eta = 0.2, max_depth = 6, 
                  subsample = 0.6,  num_class = length(table(Idents(ds2_PA))),
                  objective = "multi:softprob", eval_metric = 'mlogloss')

bst_model <- xgb.train(xgb_param, PA_train, nrounds = 100, verbose = 0)
Idents(ds2_AC) <- ds2_AC$seurat_clusters
AC_data <- get_data_table(ds2_AC, highvar = F, type = "data")
AC_data <- AC_data[selected_features,]
AC_label <- as.numeric(as.character(Idents(ds2_AC)))
colnames(AC_data) <- NULL
AC_test_data <- list(data = t(as(AC_data,"dgCMatrix")), label = AC_label)
AC_test <- xgb.DMatrix(data = AC_test_data$data,label = AC_test_data$label)

#预测结果
predict_prop_AC <-predict(bst_model, newdata = AC_test) %>%
 matrix(nrow = length(levels(Idents(ds2_PA))), 
                           ncol = ncol(ds2_AC), byrow = FALSE, 
                           dimnames = list(levels(Idents(ds2_PA)),colnames(ds2_AC)))
AC_res <- apply(predict_prop_AC,2,func,rownames(predict_prop_AC))

confuse_matrix1 <- table(AC_test_data$label, AC_res, dnn=c("true","pre"))
sankey_plot(confuse_matrix1,session = "PAtoAC")

Idents(ds2_AC) <- factor(AC_res,levels = c(0:2))
umapplot(ds2_AC)
embedding <- FetchData(object = ds2_AC, vars = c("UMAP_1", "UMAP_2"))
embedding <- cbind(embedding, t(predict_prop_AC))

ggobj <- ggplot() +
  geom_point(data = embedding[embedding$`0`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `0`), shape=16, size = 2, alpha=0.5) + 
  scale_color_gradient('0', low = "#FFFFFF00", high = "#6dc0a6") +
  new_scale("color") +
    geom_point(data = embedding[embedding$`1`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `1`),shape=16, size = 2, alpha=0.5) + 
  scale_color_gradient('1', low = "#FFFFFF00", high = "#e2b398") +
   new_scale("color") +
    geom_point(data = embedding[embedding$`2`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `2`),shape=16, size = 2, alpha=0.5) + 
  scale_color_gradient('2', low = "#FFFFFF00", high = "#e2a2ca") +
        xlab("UMAP 1") + ylab("UMAP 2")  +
        theme(axis.line = element_line(arrow = arrow(length = unit(0.2, "cm")))) +
        scale_y_continuous(breaks = NULL) +
        scale_x_continuous(breaks = NULL) + 
  theme(panel.background = element_blank(), panel.grid = element_blank(), legend.position = "bottom")
ggsave("ds2_PAtoAC_umap.svg",device = svg,plot = ggobj,height = 8,width = 8)

AC to PA

Idents(ds2_AC) <- ds2_AC$seurat_clusters
selected_features <- read.csv("./datatable/selected_features.csv", stringsAsFactors = F)
selected_features <- selected_features$x
AC_data <- get_data_table(ds2_AC, highvar = F, type = "data")
AC_data <- AC_data[selected_features,]
AC_label <- as.numeric(as.character(Idents(ds2_AC)))
colnames(AC_data) <- NULL

AC_train_data <- list(data = t(as(AC_data,"dgCMatrix")), label = AC_label)
AC_train <- xgb.DMatrix(data = AC_train_data$data,label = AC_train_data$label)
xgb_ACram <- list(eta = 0.2, max_depth = 6, 
                  subsample = 0.6,  num_class = length(table(Idents(ds2_AC))),
                  objective = "multi:softprob", eval_metric = 'mlogloss')

bst_model <- xgb.train(xgb_ACram, AC_train, nrounds = 100, verbose = 0)
Idents(ds2_PA) <- factor(ds2_PA$seurat_clusters,levels = c(0,1,2))

PA_data <- get_data_table(ds2_PA, highvar = F, type = "data")
PA_data <- PA_data[selected_features,]
PA_label <- as.numeric(as.character(Idents(ds2_PA)))
colnames(PA_data) <- NULL
PA_test_data <- list(data = t(as(PA_data,"dgCMatrix")), label = PA_label)
PA_test <- xgb.DMatrix(data = PA_test_data$data,label = PA_test_data$label)

#预测结果
predict_prop_PA <-predict(bst_model, newdata = PA_test) %>%
 matrix(nrow = length(levels(Idents(ds2_AC))), 
                           ncol = ncol(ds2_PA), byrow = FALSE, 
                           dimnames = list(levels(Idents(ds2_AC)),colnames(ds2_PA)))
PA_res <- apply(predict_prop_PA,2,func,rownames(predict_prop_PA))

confuse_matrix1 <- table(PA_test_data$label, PA_res, dnn=c("true","pre"))
sankey_plot(confuse_matrix1,session = "ACtoPA")

Idents(ds2_PA) <- factor(PA_res)
umapplot(ds2_PA)
embedding <- FetchData(object = ds2_PA, vars = c("UMAP_1", "UMAP_2"))
embedding <- cbind(embedding, t(predict_prop_PA))

ggobj <- ggplot() +
  geom_point(data = embedding[embedding$`0`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `0`), shape=16, size = 2, alpha=0.5) + 
  scale_color_gradient('0', low = "#FFFFFF00", high = "#6dc0a6") +
  new_scale("color") +
    geom_point(data = embedding[embedding$`1`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `1`),shape=16, size = 2, alpha=0.5) + 
  scale_color_gradient('1', low = "#FFFFFF00", high = "#e2b398") +
   new_scale("color") +
    geom_point(data = embedding[embedding$`2`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `2`),shape=16, size = 2, alpha=0.5) + 
  scale_color_gradient('2', low = "#FFFFFF00", high = "#e2a2ca") +
     new_scale("color") +
    geom_point(data = embedding[embedding$`3`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `3`),shape=16, size = 2, alpha=0.5) + 
  scale_color_gradient('3', low = "#FFFFFF00", high = "#d1eba8") +
        xlab("UMAP 1") + ylab("UMAP 2")  +
        theme(axis.line = element_line(arrow = arrow(length = unit(0.2, "cm")))) +
        scale_y_continuous(breaks = NULL) +
        scale_x_continuous(breaks = NULL) + 
  theme(panel.background = element_blank(), panel.grid = element_blank(), legend.position = "bottom")
ggsave("ds2_ACtoPA_umap.svg",device = svg,plot = ggobj,height = 8,width = 8)

在ds0上训练

Idents(ds0) <- ds0$seurat_clusters
ds0_data <- get_data_table(ds0, highvar = F, type = "data")
ds0_label <- as.numeric(as.character(Idents(ds0)))

index <- c(1:dim(ds0_data)[2]) %>% sample(ceiling(0.3*dim(ds0_data)[2]), replace = F, prob = NULL)
colnames(ds0_data) <- NULL

ds0_train_data <- list(data = t(as(ds0_data[,-index],"dgCMatrix")), label = ds0_label[-index])
ds0_test_data <- list(data = t(as(ds0_data[,index],"dgCMatrix")), label = ds0_label[index])

ds0_train <- xgb.DMatrix(data = ds0_train_data$data,label = ds0_train_data$label)
ds0_test <- xgb.DMatrix(data = ds0_test_data$data,label = ds0_test_data$label)

watchlist <- list(train = ds0_train, eval = ds0_test)
xgb_param <- list(eta = 0.2, max_depth = 6, 
                  subsample = 0.6,  num_class = length(table(Idents(ds0))),
                  objective = "multi:softprob", eval_metric = 'mlogloss')

bst_model <- xgb.train(xgb_param, ds0_train, nrounds = 100, watchlist, verbose = 0)
saveRDS(bst_model, "ds0_model.rds")
eval_loss <- bst_model[["evaluation_log"]][["eval_mlogloss"]]
plot_ly(data.frame(eval_loss), x = c(1:100), y = eval_loss) %>% 
  add_trace(type = "scatter", mode = "markers+lines", 
            marker = list(color = "black", line = list(color = "#1E90FFC7", width = 1)),
            line = list(color = "#1E90FF80", width = 2)) %>% 
  layout(xaxis = list(title = "epoch"),yaxis = list(title = "eval_mlogloss"))
importance <- xgb.importance(colnames(ds0_train), model = bst_model)
head(importance)
xgb.ggplot.importance(head(importance,20),n_clusters = 1) + theme_bw()+theme(
    axis.title.x = element_text(size = 15), axis.text.x = element_text(size = 8, colour = "black"),
    axis.title.y = element_text(size = 15), axis.text.y = element_text(size = 12, colour = "black"),
    legend.text = element_text(size = 20), legend.title = element_blank(), panel.grid = element_blank())
write.csv(importance, "./datatable/ds0_features.csv", row.names = F)
multi_featureplot(head(importance,9)$Feature, ds0, labels = "") 

ds0 -> ds2

ARI = 0.4015002

Idents(ds2) <- ds2$seurat_clusters 
temp <- get_data_table(ds2, highvar = F, type = "data")
ds2_data <- matrix(data=0, nrow = length(rownames(ds0_data)), ncol = length(colnames(temp)), 
                   byrow = FALSE, dimnames = list(rownames(ds0_data),colnames(temp)))
for(i in intersect(rownames(ds2_data), rownames(temp))){
  ds2_data[i,] <- temp[i,]
}
rm(temp)
ds2_label <- as.numeric(as.character(Idents(ds2)))
colnames(ds2_data) <- NULL
ds2_test_data <- list(data = t(as(ds2_data,"dgCMatrix")), label = ds2_label)
ds2_test <- xgb.DMatrix(data = ds2_test_data$data,label = ds2_test_data$label)

#预测结果

predict_ds2_test <- predict(bst_model, newdata = ds2_test)

predict_prop_ds2 <- matrix(data=predict_ds2_test, nrow = bst_model[["params"]][["num_class"]], 
                           ncol = ncol(ds2), byrow = FALSE, 
                           dimnames = list(c(0:(bst_model[["params"]][["num_class"]]-1)),colnames(ds2)))

## 得到分群结果
ds2_res <- apply(predict_prop_ds2,2,func,rownames(predict_prop_ds2))

adjustedRandIndex(ds2_res, ds2_test_data$label)
confuse_matrix1 <- table(ds2_test_data$label, ds2_res, dnn=c("true","pre"))

sankey_plot(confuse_matrix1,0:5,0:4,session = "ds0tods2")

Idents(ds2) <- factor(ds2_res,levels = c(0:5))
umapplot(ds2)
embedding <- FetchData(object = ds2, vars = c("UMAP_1", "UMAP_2"))
embedding <- cbind(embedding, t(predict_prop_ds2))

ggobj <- ggplot() +
  geom_point(data = embedding[embedding$`0`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `0`), shape=16, size = 2, alpha=0.5) + 
  scale_color_gradient('0', low = "#FFFFFF00", high = "#6dc0a6") +
  new_scale("color") +
    geom_point(data = embedding[embedding$`1`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `1`),shape=16, size = 2, alpha=0.5) + 
  scale_color_gradient('1', low = "#FFFFFF00", high = "#e2b398") +
   new_scale("color") +
    geom_point(data = embedding[embedding$`2`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `2`),shape=16, size = 2, alpha=0.5) + 
  scale_color_gradient('2', low = "#FFFFFF00", high = "#e2a2ca") +
     new_scale("color") +
    geom_point(data = embedding[embedding$`3`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `3`),shape=16, size = 2, alpha=0.5) + 
  scale_color_gradient('3', low = "#FFFFFF00", high = "#d1eba8") +
     new_scale("color") +
    geom_point(data = embedding[embedding$`4`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `4`),shape=16, size = 2, alpha=0.5) + 
  scale_color_gradient('4', low = "#FFFFFF00", high = "#b1d6fb") +
     new_scale("color") +
    geom_point(data = embedding[embedding$`5`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `5`),shape=16, size = 2, alpha=0.5) + 
  scale_color_gradient('5', low = "#FFFFFF00", high = "#fd9999") +
        xlab("UMAP 1") + ylab("UMAP 2")  +
        theme(axis.line = element_line(arrow = arrow(length = unit(0.2, "cm")))) +
        scale_y_continuous(breaks = NULL) +
        scale_x_continuous(breaks = NULL) + 
  theme(panel.background = element_blank(), panel.grid = element_blank(), legend.position = "bottom")
ggsave("ds0tods2umap.svg",device = svg,plot = ggobj,height = 8,width = 8)

ds0 -> ds1

ARI = 0.2524238

Idents(ds1) <- ds1$seurat_clusters
temp <- get_data_table(ds1, highvar = F, type = "data")
ds1_data <- matrix(data=0, nrow = length(rownames(ds0_data)), ncol = length(colnames(temp)), 
                   byrow = FALSE, dimnames = list(rownames(ds0_data),colnames(temp)))
for(i in intersect(rownames(ds1_data), rownames(temp))){
  ds1_data[i,] <- temp[i,]
}
rm(temp)
ds1_label <- as.numeric(as.character(Idents(ds1)))
colnames(ds1_data) <- NULL
ds1_test_data <- list(data = t(as(ds1_data,"dgCMatrix")), label = ds1_label)
ds1_test <- xgb.DMatrix(data = ds1_test_data$data,label = ds1_test_data$label)

#预测结果

predict_ds1_test <- predict(bst_model, newdata = ds1_test)

predict_prop_ds1 <- matrix(data=predict_ds1_test, nrow = bst_model[["params"]][["num_class"]], 
                           ncol = ncol(ds1), byrow = FALSE, 
                           dimnames = list(c(0:(bst_model[["params"]][["num_class"]]-1)),colnames(ds1)))

## 得到分群结果
ds1_res <- apply(predict_prop_ds1,2,func,rownames(predict_prop_ds1))
adjustedRandIndex(ds1_test_data$label, ds1_res)
Idents(ds1) <- factor(ds1_res,levels = c(0:5))
umapplot(ds1)
# umapplot(ds1,group.by = "Classification1")
confuse_matrix <- table(ds1_test_data$label, ds1_res, dnn=c("true","pre"))
sankey_plot(confuse_matrix,c(0:4),c(0:4),session = "ds0tods1")
embedding <- FetchData(object = ds1, vars = c("UMAP_1", "UMAP_2"))
embedding <- cbind(embedding, t(predict_prop_ds1))

ggobj <- ggplot() +
  geom_point(data = embedding[embedding$`0`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `0`), shape=16, size = 2, alpha=0.5) + 
  scale_color_gradient('0', low = "#FFFFFF00", high = "#6dc0a6") +
  new_scale("color") +
    geom_point(data = embedding[embedding$`1`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `1`),shape=16, size = 2, alpha=0.5) + 
  scale_color_gradient('1', low = "#FFFFFF00", high = "#e2b398") +
   new_scale("color") +
    geom_point(data = embedding[embedding$`2`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `2`),shape=16, size = 2, alpha=0.5) + 
  scale_color_gradient('2', low = "#FFFFFF00", high = "#e2a2ca") +
     new_scale("color") +
    geom_point(data = embedding[embedding$`3`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `3`),shape=16, size = 2, alpha=0.5) + 
  scale_color_gradient('3', low = "#FFFFFF00", high = "#d1eba8") +
     new_scale("color") +
    geom_point(data = embedding[embedding$`4`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `4`),shape=16, size = 2, alpha=0.5) + 
  scale_color_gradient('4', low = "#FFFFFF00", high = "#b1d6fb") +
     new_scale("color") +
    geom_point(data = embedding[embedding$`5`>0.1,], 
             aes(x = UMAP_1, y = UMAP_2, color = `5`),shape=16, size = 2, alpha=0.5) + 
  scale_color_gradient('5', low = "#FFFFFF00", high = "#fd9999") +
        xlab("UMAP 1") + ylab("UMAP 2")  +
        theme(axis.line = element_line(arrow = arrow(length = unit(0.2, "cm")))) +
        scale_y_continuous(breaks = NULL) +
        scale_x_continuous(breaks = NULL) + 
  theme(panel.background = element_blank(), panel.grid = element_blank(), legend.position = "bottom")
ggsave("ds0tods1umap.svg",device = svg,plot = ggobj,height = 8,width = 8)

ARI 和聚类数的关系

lym_ds2 <- readRDS("./lym_ds2.rds")

Idents(lym_ds2) <- lym_ds2$conditions
lym_ds2_AC <- subset(lym_ds2, idents = "AC")
lym_ds2_PA <- subset(lym_ds2, idents = "PA")

set.seed(7)
lym_PA_data <- get_data_table(lym_ds2_PA, highvar = T, type = "data")
lym_AC_data <- get_data_table(lym_ds2_AC, highvar = T, type = "data")
res <- list()
for(reso in seq(0.05,0.3,0.05))
{
  lym_ds2_AC <- lym_ds2_AC %>% FindNeighbors(dims = 1:20) %>% FindClusters(resolution = reso)
  lym_ds2_PA <- lym_ds2_PA %>% FindNeighbors(dims = 1:20) %>% FindClusters(resolution = reso)

  lym_PA_label <- as.numeric(as.character(Idents(lym_ds2_PA)))

  index <- c(1:dim(lym_PA_data)[2]) %>% sample(ceiling(0.3*dim(lym_PA_data)[2]), replace = F, prob = NULL)
  colnames(lym_PA_data) <- NULL
  lym_PA_train_data <- list(data = t(as(lym_PA_data[,-index],"dgCMatrix")), label = lym_PA_label[-index])
  lym_PA_test_data <- list(data = t(as(lym_PA_data[,index],"dgCMatrix")), label = lym_PA_label[index])
  
  lym_PA_train <- xgb.DMatrix(data = lym_PA_train_data$data,label = lym_PA_train_data$label)
  lym_PA_test <- xgb.DMatrix(data = lym_PA_test_data$data,label = lym_PA_test_data$label)
  
  watchlist <- list(train = lym_PA_train, eval = lym_PA_test)
  xgb_param <- list(eta = 0.2, max_depth = 6, 
                    subsample = 0.6,  num_class = length(table(Idents(lym_ds2_PA))),
                    objective = "multi:softmax", eval_metric = 'mlogloss')
  
  bst_model <- xgb.train(xgb_param, lym_PA_train, nrounds = 100, watchlist, verbose = 0)
  
  
  lym_AC_label <- as.numeric(as.character(Idents(lym_ds2_AC)))
  colnames(lym_AC_data) <- NULL
  lym_AC_test_data <- list(data = t(as(lym_AC_data,"dgCMatrix")), label = lym_AC_label)
  lym_AC_test <- xgb.DMatrix(data = lym_AC_test_data$data,label = lym_AC_test_data$label)
  predict_lym_AC_test <- round(predict(bst_model, newdata = lym_AC_test))
  
  res <- append(x = res,values = adjustedRandIndex(predict_lym_AC_test, lym_AC_test_data$label))
}
res

ARI 和聚类数的关系

 ds2 <- readRDS("./ds2.rds")

Idents( ds2) <-  ds2$conditions
 ds2_AC <- subset( ds2, idents = "AC")
 ds2_PA <- subset( ds2, idents = "PA")

set.seed(7)
 PA_data <- get_data_table( ds2_PA, highvar = T, type = "data")
 AC_data <- get_data_table( ds2_AC, highvar = T, type = "data")
ds2_res <- list()
for(reso in seq(0.05,0.3,0.05))
{
   ds2_AC <-  ds2_AC %>% FindNeighbors(dims = 1:20) %>% FindClusters(resolution = reso)
   ds2_PA <-  ds2_PA %>% FindNeighbors(dims = 1:20) %>% FindClusters(resolution = reso)

   PA_label <- as.numeric(as.character(Idents( ds2_PA)))

  index <- c(1:dim( PA_data)[2]) %>% sample(ceiling(0.3*dim( PA_data)[2]), replace = F, prob = NULL)
  colnames( PA_data) <- NULL
   PA_train_data <- list(data = t(as( PA_data[,-index],"dgCMatrix")), label =  PA_label[-index])
   PA_test_data <- list(data = t(as( PA_data[,index],"dgCMatrix")), label =  PA_label[index])
  
   PA_train <- xgb.DMatrix(data =  PA_train_data$data,label =  PA_train_data$label)
   PA_test <- xgb.DMatrix(data =  PA_test_data$data,label =  PA_test_data$label)
  
  watchlist <- list(train =  PA_train, eval =  PA_test)
  xgb_param <- list(eta = 0.2, max_depth = 6, 
                    subsample = 0.6,  num_class = length(table(Idents( ds2_PA))),
                    objective = "multi:softmax", eval_metric = 'mlogloss')
  
  bst_model <- xgb.train(xgb_param,  PA_train, nrounds = 100, watchlist, verbose = 0)
  
  
   AC_label <- as.numeric(as.character(Idents( ds2_AC)))
  colnames( AC_data) <- NULL
   AC_test_data <- list(data = t(as( AC_data,"dgCMatrix")), label =  AC_label)
   AC_test <- xgb.DMatrix(data =  AC_test_data$data,label =  AC_test_data$label)
  predict_AC_test <- round(predict(bst_model, newdata = AC_test))
  
  ds2_res <- append(x = ds2_res,values = adjustedRandIndex(predict_AC_test,  AC_test_data$label))
}
ds2_res

绘图

plot_ly(data, x = ~resolution) %>% 
  add_trace(y = ~lym_ARI, name = 'lym', mode = 'lines+markers',size = 4, markers = list(color = "#158aff"),
            line = list(color = '#b1d6fb', width = 4), type = 'scatter') %>%
  add_trace(y = ~SMC_ARI, name = 'SMC', mode = 'lines+markers',size = 4, markers = list(color = "#ff2121"), 
            line = list(color = '#e2a2ca', width = 4), type = 'scatter')%>%
  layout(xaxis = list(zerolinecolor = '#ffffff', zerolinewidth = 2, range = c(0, 0.32),
                       gridcolor = 'ffff', dtick=0.05, title = "resolution"),
         yaxis = list(zerolinecolor = '#ffffff', zerolinewidth = 2, range = c(0.2, 0.9),
                       gridcolor = 'ffff', dtick=0.1,  title = "ARI")) %>%
  layout(font = list(family = "Arial", size = 20, color = "black"))
Warning: 'scatter' objects don't have these attributes: 'markers'
Valid attributes include:
'cliponaxis', 'connectgaps', 'customdata', 'customdatasrc', 'dx', 'dy', 'error_x', 'error_y', 'fill', 'fillcolor', 'groupnorm', 'hoverinfo', 'hoverinfosrc', 'hoverlabel', 'hoveron', 'hovertemplate', 'hovertemplatesrc', 'hovertext', 'hovertextsrc', 'ids', 'idssrc', 'legendgroup', 'legendgrouptitle', 'legendrank', 'line', 'marker', 'meta', 'metasrc', 'mode', 'name', 'opacity', 'orientation', 'selected', 'selectedpoints', 'showlegend', 'stackgaps', 'stackgroup', 'stream', 'text', 'textfont', 'textposition', 'textpositionsrc', 'textsrc', 'texttemplate', 'texttemplatesrc', 'transforms', 'type', 'uid', 'uirevision', 'unselected', 'visible', 'x', 'x0', 'xaxis', 'xcalendar', 'xhoverformat', 'xperiod', 'xperiod0', 'xperiodalignment', 'xsrc', 'y', 'y0', 'yaxis', 'ycalendar', 'yhoverformat', 'yperiod', 'yperiod0', 'yperiodalignment', 'ysrc', 'key', 'set', 'frame', 'transforms', '_isNestedKey', '_isSimpleKey', '_isG [... truncated]
Warning: 'scatter' objects don't have these attributes: 'markers'
Valid attributes include:
'cliponaxis', 'connectgaps', 'customdata', 'customdatasrc', 'dx', 'dy', 'error_x', 'error_y', 'fill', 'fillcolor', 'groupnorm', 'hoverinfo', 'hoverinfosrc', 'hoverlabel', 'hoveron', 'hovertemplate', 'hovertemplatesrc', 'hovertext', 'hovertextsrc', 'ids', 'idssrc', 'legendgroup', 'legendgrouptitle', 'legendrank', 'line', 'marker', 'meta', 'metasrc', 'mode', 'name', 'opacity', 'orientation', 'selected', 'selectedpoints', 'showlegend', 'stackgaps', 'stackgroup', 'stream', 'text', 'textfont', 'textposition', 'textpositionsrc', 'textsrc', 'texttemplate', 'texttemplatesrc', 'transforms', 'type', 'uid', 'uirevision', 'unselected', 'visible', 'x', 'x0', 'xaxis', 'xcalendar', 'xhoverformat', 'xperiod', 'xperiod0', 'xperiodalignment', 'xsrc', 'y', 'y0', 'yaxis', 'ycalendar', 'yhoverformat', 'yperiod', 'yperiod0', 'yperiodalignment', 'ysrc', 'key', 'set', 'frame', 'transforms', '_isNestedKey', '_isSimpleKey', '_isG [... truncated]
Warning: 'scatter' objects don't have these attributes: 'markers'
Valid attributes include:
'cliponaxis', 'connectgaps', 'customdata', 'customdatasrc', 'dx', 'dy', 'error_x', 'error_y', 'fill', 'fillcolor', 'groupnorm', 'hoverinfo', 'hoverinfosrc', 'hoverlabel', 'hoveron', 'hovertemplate', 'hovertemplatesrc', 'hovertext', 'hovertextsrc', 'ids', 'idssrc', 'legendgroup', 'legendgrouptitle', 'legendrank', 'line', 'marker', 'meta', 'metasrc', 'mode', 'name', 'opacity', 'orientation', 'selected', 'selectedpoints', 'showlegend', 'stackgaps', 'stackgroup', 'stream', 'text', 'textfont', 'textposition', 'textpositionsrc', 'textsrc', 'texttemplate', 'texttemplatesrc', 'transforms', 'type', 'uid', 'uirevision', 'unselected', 'visible', 'x', 'x0', 'xaxis', 'xcalendar', 'xhoverformat', 'xperiod', 'xperiod0', 'xperiodalignment', 'xsrc', 'y', 'y0', 'yaxis', 'ycalendar', 'yhoverformat', 'yperiod', 'yperiod0', 'yperiodalignment', 'ysrc', 'key', 'set', 'frame', 'transforms', '_isNestedKey', '_isSimpleKey', '_isG [... truncated]
Warning: 'scatter' objects don't have these attributes: 'markers'
Valid attributes include:
'cliponaxis', 'connectgaps', 'customdata', 'customdatasrc', 'dx', 'dy', 'error_x', 'error_y', 'fill', 'fillcolor', 'groupnorm', 'hoverinfo', 'hoverinfosrc', 'hoverlabel', 'hoveron', 'hovertemplate', 'hovertemplatesrc', 'hovertext', 'hovertextsrc', 'ids', 'idssrc', 'legendgroup', 'legendgrouptitle', 'legendrank', 'line', 'marker', 'meta', 'metasrc', 'mode', 'name', 'opacity', 'orientation', 'selected', 'selectedpoints', 'showlegend', 'stackgaps', 'stackgroup', 'stream', 'text', 'textfont', 'textposition', 'textpositionsrc', 'textsrc', 'texttemplate', 'texttemplatesrc', 'transforms', 'type', 'uid', 'uirevision', 'unselected', 'visible', 'x', 'x0', 'xaxis', 'xcalendar', 'xhoverformat', 'xperiod', 'xperiod0', 'xperiodalignment', 'xsrc', 'y', 'y0', 'yaxis', 'ycalendar', 'yhoverformat', 'yperiod', 'yperiod0', 'yperiodalignment', 'ysrc', 'key', 'set', 'frame', 'transforms', '_isNestedKey', '_isSimpleKey', '_isG [... truncated]

解释性

library(DALEX)
library(modelStudio)
source("tianfengRwrappers.R")
ds2 <- readRDS("ds2.rds")
Idents(ds2) <- ds2$Classification1
ds2 <- RenameIdents(ds2, 'SMC1' = 0, 'Fibromyocyte' = 1, 'Pericyte' = 2, 'Fibroblast' = 3, 'SMC2' = 4)
ds2_data <- get_data_table(ds2, highvar = T, type = "data")
ds2_label <- as.numeric(as.character(Idents(ds2)))
index <- c(1:dim(ds2_data)[2]) %>% sample(ceiling(0.3*dim(ds2_data)[2]), replace = F, prob = NULL)
# colnames(ds2_data) <- NULL
ds2_train_data <- list(data = t(as(ds2_data[,-index],"dgCMatrix")), label = ds2_label[-index])
ds2_test_data <- list(data = t(as(ds2_data[,index],"dgCMatrix")), label = ds2_label[index])
ds2_train <- xgb.DMatrix(data = ds2_train_data$data,label = ds2_train_data$label)
ds2_test <- xgb.DMatrix(data = ds2_test_data$data,label = ds2_test_data$label)
watchlist <- list(train = ds2_train, eval = ds2_test)
xgb_param <- list(eta = 0.2, max_depth = 6, 
                  subsample = 0.6,  num_class = length(table(Idents(ds2))),
                  objective = "multi:softprob", eval_metric = 'mlogloss')
bst_model <- xgb.train(xgb_param, ds2_train, nrounds = 100, watchlist, verbose = 0)

# saveRDS(bst_model,"reduced_ds2model.rds")
# 建立解释器
explainer_xgb <- DALEX::explain(bst_model, data = ds2_train_data$data[1:200,], y = ds2_train_data$label[1:200], label = "XGBoost")

# modelStudio::modelStudio(explainer = explainer_xgb, new_observation = ds2_train_data$data[1:2,] )
p1 <- DALEX::variable_profile(explainer_xgb, variable = "ACKR1", type = "accumulated")
plot(p1)
p2 <- single_variable(explainer_xgb, rownames(ds2_data)[1:10], type = "pdp")
plot(p2)
# p3 <- variable_importance(explainer_xgb, variable =rownames(ds2_data)[1:10],loss_function = loss_root_mean_square)
reduced_ds2model <- readRDS("reduced_ds2model.rds")
p1 <- xgb.plot.tree(feature_names = rownames(ds2_data), model = reduced_ds2model,render = F)
DiagrammeR::render_graph(p1)
saveRDS(p1,"ds2_tree.rds")
p2 <- xgb.ggplot.shap.summary(data = ds2_train_data$data, features = rownames(ds2_data)[1:10], model = reduced_ds2model)

xgb.ggplot.deepness(model = reduced_ds2model, which = c("2x1", "max.depth", "med.depth", "med.weight"))

shapvalues <- xgb.plot.shap(data = ds2_train_data$data, features = rownames(ds2_data)[1:10], model = reduced_ds2model,plot = F)
library(SHAPforxgboost)

shap_contrib <- predict(reduced_ds2model, ds2_train_data$data, predcontrib = TRUE)
shap_contrib <- data.frame(shap_contrib[[4]]) ##提取SMC1对应的分类
BIAS0 <- shap_contrib[, ncol(shap_contrib)][1]
shap_contrib[,"BIAS"] <- NULL
imp <- colMeans(abs(shap_contrib))
mean_shap_score <- imp[order(imp, decreasing = T)]

shap_values<- list(shap_score = shap_contrib, mean_shap_score = mean_shap_score, 
    BIAS0 = BIAS0)

# To prepare the long-format data:
shap_long <- shap.prep(shap_contrib = shap_contrib, X_train = ds2_train_data$data,top_n = 30)

shap.plot.summary(shap_long, x_bound  = 1.2, dilute = 30)
shap.plot.force_plot(plot_data,zoom_in_location = 800)
Data has N = 6675 | zoom in length is 150 at location 800.

shap.plot.dependence(data_long = shap_long, x= "CFH", color_feature = "CFH") + mytheme + geom_smooth(method = "loess")+ scale_colour_gradient(low="#1E90FF", high="#ff2121") +scale_x_continuous(limits = c(0,4))
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
`geom_smooth()` using formula 'y ~ x'
Warning: Removed 6 rows containing non-finite values (stat_smooth).
`geom_smooth()` using formula 'y ~ x'
Warning: Removed 6 rows containing non-finite values (stat_smooth).
Warning: Removed 6 rows containing missing values (geom_point).

shap.plot.dependence(data_long = shap_long, data_int = shap_int[[2]],
                           x= "LUM", y = "LUM", color_feature = "LUM")
`geom_smooth()` using formula 'y ~ x'
Warning in simpleLoess(y, x, w, span, degree = degree, parametric = parametric,  :
  pseudoinverse used at -0.015223
Warning in simpleLoess(y, x, w, span, degree = degree, parametric = parametric,  :
  neighborhood radius 1.807
Warning in simpleLoess(y, x, w, span, degree = degree, parametric = parametric,  :
  reciprocal condition number  1.5904e-16
Warning in simpleLoess(y, x, w, span, degree = degree, parametric = parametric,  :
  There are other near singularities as well. 3.2104

data("iris")
X1 = as.matrix(iris[,-5])
mod1 = xgboost::xgboost(
  data = X1, label = iris$Species, gamma = 0, eta = 1,
  lambda = 0, nrounds = 1, verbose = FALSE)

# shap.values(model, X_dataset) returns the SHAP
# data matrix and ranked features by mean|SHAP|
shap_values <- shap.values(xgb_model = mod1, X_train = X1)
shap_values$mean_shap_score
Petal.Length  Petal.Width Sepal.Length  Sepal.Width 
  0.62935975   0.21664035   0.02910357   0.00000000 
shap_values_iris <- shap_values$shap_score

confidence曲线

Add a new chunk by clicking the Insert Chunk button on the toolbar or by pressing Ctrl+Alt+I.

When you save the notebook, an HTML file containing the code and output will be saved alongside it (click the Preview button or press Ctrl+Shift+K to preview the HTML file).

The preview shows you a rendered HTML copy of the contents of the editor. Consequently, unlike Knit, Preview does not run any R code chunks. Instead, the output of the chunk when it was last run in the editor is displayed.

LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKVGhpcyBpcyBhbiBbUiBNYXJrZG93bl0oaHR0cDovL3JtYXJrZG93bi5yc3R1ZGlvLmNvbSkgTm90ZWJvb2suIFdoZW4geW91IGV4ZWN1dGUgY29kZSB3aXRoaW4gdGhlIG5vdGVib29rLCB0aGUgcmVzdWx0cyBhcHBlYXIgYmVuZWF0aCB0aGUgY29kZS4gCgpUcnkgZXhlY3V0aW5nIHRoaXMgY2h1bmsgYnkgY2xpY2tpbmcgdGhlICpSdW4qIGJ1dHRvbiB3aXRoaW4gdGhlIGNodW5rIG9yIGJ5IHBsYWNpbmcgeW91ciBjdXJzb3IgaW5zaWRlIGl0IGFuZCBwcmVzc2luZyAqQ3RybCtTaGlmdCtFbnRlciouIAoKYGBge3J9CnNvdXJjZSgidGlhbmZlbmdSd3JhcHBlcnMuUiIpCmxpYnJhcnkoeGdib29zdCkKbGlicmFyeShNYXRyaXgpCmxpYnJhcnkobWNsdXN0KQpsaWJyYXJ5KHRpZHl2ZXJzZSkKYGBgCmBgYHtyfQpkczIgPC0gcmVhZFJEUygiZHMyLnJkcyIpCklkZW50cyhkczIpIDwtIGRzMiRzZXVyYXRfY2x1c3RlcnMKSWRlbnRzKGRzMSkgPC0gZHMxJHNldXJhdF9jbHVzdGVycwpJZGVudHMoZHMwKSA8LSBkczAkc2V1cmF0X2NsdXN0ZXJzCmBgYAoKCiMjIOaVsOWAvOWMlgojIyMgZHMy6K6t57uD5YiG57G75ZmoCmBgYHtyfQpkczJfZGF0YSA8LSBnZXRfZGF0YV90YWJsZShkczIsIGhpZ2h2YXIgPSBGLCB0eXBlID0gImRhdGEiKQpkczJfbGFiZWwgPC0gYXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIoSWRlbnRzKGRzMikpKQoKaW5kZXggPC0gYygxOmRpbShkczJfZGF0YSlbMl0pICU+JSBzYW1wbGUoY2VpbGluZygwLjMqZGltKGRzMl9kYXRhKVsyXSksIHJlcGxhY2UgPSBGLCBwcm9iID0gTlVMTCkKY29sbmFtZXMoZHMyX2RhdGEpIDwtIE5VTEwKCmRzMl90cmFpbl9kYXRhIDwtIGxpc3QoZGF0YSA9IHQoYXMoZHMyX2RhdGFbLC1pbmRleF0sImRnQ01hdHJpeCIpKSwgbGFiZWwgPSBkczJfbGFiZWxbLWluZGV4XSkKZHMyX3Rlc3RfZGF0YSA8LSBsaXN0KGRhdGEgPSB0KGFzKGRzMl9kYXRhWyxpbmRleF0sImRnQ01hdHJpeCIpKSwgbGFiZWwgPSBkczJfbGFiZWxbaW5kZXhdKQoKZHMyX3RyYWluIDwtIHhnYi5ETWF0cml4KGRhdGEgPSBkczJfdHJhaW5fZGF0YSRkYXRhLGxhYmVsID0gZHMyX3RyYWluX2RhdGEkbGFiZWwpCmRzMl90ZXN0IDwtIHhnYi5ETWF0cml4KGRhdGEgPSBkczJfdGVzdF9kYXRhJGRhdGEsbGFiZWwgPSBkczJfdGVzdF9kYXRhJGxhYmVsKQoKd2F0Y2hsaXN0IDwtIGxpc3QodHJhaW4gPSBkczJfdHJhaW4sIGV2YWwgPSBkczJfdGVzdCkKeGdiX3BhcmFtIDwtIGxpc3QoZXRhID0gMC4yLCBtYXhfZGVwdGggPSA2LCAKICAgICAgICAgICAgICAgICAgc3Vic2FtcGxlID0gMC42LCAgbnVtX2NsYXNzID0gbGVuZ3RoKHRhYmxlKElkZW50cyhkczIpKSksCiAgICAgICAgICAgICAgICAgIG9iamVjdGl2ZSA9ICJtdWx0aTpzb2Z0cHJvYiIsIGV2YWxfbWV0cmljID0gJ21sb2dsb3NzJykKCmJzdF9tb2RlbCA8LSB4Z2IudHJhaW4oeGdiX3BhcmFtLCBkczJfdHJhaW4sIG5yb3VuZHMgPSAxMDAsIHdhdGNobGlzdCwgdmVyYm9zZSA9IDApCnNhdmVSRFMoYnN0X21vZGVsLCAiZHMyX21vZGVsLnJkcyIpCmV2YWxfbG9zcyA8LSBic3RfbW9kZWxbWyJldmFsdWF0aW9uX2xvZyJdXVtbImV2YWxfbWxvZ2xvc3MiXV0KcGxvdF9seShkYXRhLmZyYW1lKGV2YWxfbG9zcyksIHggPSBjKDE6MTAwKSwgeSA9IGV2YWxfbG9zcykgJT4lIAogIGFkZF90cmFjZSh0eXBlID0gInNjYXR0ZXIiLCBtb2RlID0gIm1hcmtlcnMrbGluZXMiLCAKICAgICAgICAgICAgbWFya2VyID0gbGlzdChjb2xvciA9ICJibGFjayIsIGxpbmUgPSBsaXN0KGNvbG9yID0gIiMxRTkwRkZDNyIsIHdpZHRoID0gMSkpLAogICAgICAgICAgICBsaW5lID0gbGlzdChjb2xvciA9ICIjMUU5MEZGODAiLCB3aWR0aCA9IDIpKSAlPiUgCiAgbGF5b3V0KHhheGlzID0gbGlzdCh0aXRsZSA9ICJlcG9jaCIpLHlheGlzID0gbGlzdCh0aXRsZSA9ICJldmFsX21sb2dsb3NzIikpCmBgYAoKYGBge3IgZmlnLmhlaWdodD02LGZpZy53aWR0aD02fQppbXBvcnRhbmNlIDwtIHhnYi5pbXBvcnRhbmNlKGNvbG5hbWVzKGRzMl90cmFpbiksIG1vZGVsID0gYnN0X21vZGVsKQpoZWFkKGltcG9ydGFuY2UpCnhnYi5nZ3Bsb3QuaW1wb3J0YW5jZShoZWFkKGltcG9ydGFuY2UsMjApLG5fY2x1c3RlcnMgPSAxKSArIHRoZW1lX2J3KCkrdGhlbWUoCiAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE1KSwgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBjb2xvdXIgPSAiYmxhY2siKSwKICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTUpLCBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGNvbG91ciA9ICJibGFjayIpLAogICAgbGVnZW5kLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDIwKSwgbGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpLCBwYW5lbC5ncmlkID0gZWxlbWVudF9ibGFuaygpKQpgYGAKCgojIyBkczIgLT4gZHMxCiMjIyBBUkkgPSAwLjE2OTU0MTcKYGBge3J9CklkZW50cyhkczEpIDwtIGRzMSRzZXVyYXRfY2x1c3RlcnMKdGVtcCA8LSBnZXRfZGF0YV90YWJsZShkczEsIGhpZ2h2YXIgPSBGLCB0eXBlID0gImRhdGEiKQpkczFfZGF0YSA8LSBtYXRyaXgoZGF0YT0wLCBucm93ID0gbGVuZ3RoKHJvd25hbWVzKGRzMl9kYXRhKSksIG5jb2wgPSBsZW5ndGgoY29sbmFtZXModGVtcCkpLCAKICAgICAgICAgICAgICAgICAgIGJ5cm93ID0gRkFMU0UsIGRpbW5hbWVzID0gbGlzdChyb3duYW1lcyhkczJfZGF0YSksY29sbmFtZXModGVtcCkpKQpmb3IoaSBpbiBpbnRlcnNlY3Qocm93bmFtZXMoZHMyX2RhdGEpLCByb3duYW1lcyh0ZW1wKSkpewogIGRzMV9kYXRhW2ksXSA8LSB0ZW1wW2ksXQp9CnJtKHRlbXApCmRzMV9sYWJlbCA8LSBhcy5udW1lcmljKGFzLmNoYXJhY3RlcihJZGVudHMoZHMxKSkpCmNvbG5hbWVzKGRzMV9kYXRhKSA8LSBOVUxMCmRzMV90ZXN0X2RhdGEgPC0gbGlzdChkYXRhID0gdChhcyhkczFfZGF0YSwiZGdDTWF0cml4IikpLCBsYWJlbCA9IGRzMV9sYWJlbCkKZHMxX3Rlc3QgPC0geGdiLkRNYXRyaXgoZGF0YSA9IGRzMV90ZXN0X2RhdGEkZGF0YSxsYWJlbCA9IGRzMV90ZXN0X2RhdGEkbGFiZWwpCgoj6aKE5rWL57uT5p6cCnByZWRpY3RfZHMxX3Rlc3QgPC0gcHJlZGljdChic3RfbW9kZWwsIG5ld2RhdGEgPSBkczFfdGVzdCkKCnByZWRpY3RfcHJvcF9kczEgPC0gbWF0cml4KGRhdGE9cHJlZGljdF9kczFfdGVzdCwgbnJvdyA9IGxlbmd0aChsZXZlbHMoSWRlbnRzKGRzMikpKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG5jb2wgPSBuY29sKGRzMSksIGJ5cm93ID0gRkFMU0UsIAogICAgICAgICAgICAgICAgICAgICAgICAgICBkaW1uYW1lcyA9IGxpc3QobGV2ZWxzKElkZW50cyhkczIpKSxjb2xuYW1lcyhkczEpKSkKCiMjIOW+l+WIsOWIhue+pOe7k+aenApkczFfcmVzIDwtIGFwcGx5KHByZWRpY3RfcHJvcF9kczEsMixmdW5jLHJvd25hbWVzKHByZWRpY3RfcHJvcF9kczEpKQphZGp1c3RlZFJhbmRJbmRleChkczFfcmVzLCBkczFfdGVzdF9kYXRhJGxhYmVsKQoKSWRlbnRzKGRzMSkgPC0gZmFjdG9yKGRzMV9yZXMsbGV2ZWxzID0gYygwOjQpKQp1bWFwcGxvdChkczEpCmRzMSRzdXBjbHVzdGVyaW5nIDwtIElkZW50cyhkczEpICPkv53lrZjnm5HnnaPogZrnsbvnu5PmnpwKYGBgCgojIyDmlbDlgLzljJblnLDmipXlsITlm551bWFwCmBgYHtyfQplbWJlZGRpbmcgPC0gRmV0Y2hEYXRhKG9iamVjdCA9IGRzMSwgdmFycyA9IGMoIlVNQVBfMSIsICJVTUFQXzIiKSkKZW1iZWRkaW5nIDwtIGNiaW5kKGVtYmVkZGluZywgdChwcmVkaWN0X3Byb3BfZHMxKSkKCmdnb2JqIDwtIGdncGxvdCgpICsKICBnZW9tX3BvaW50KGRhdGEgPSBlbWJlZGRpbmdbZW1iZWRkaW5nJGAwYD4wLjEsXSwgCiAgICAgICAgICAgICBhZXMoeCA9IFVNQVBfMSwgeSA9IFVNQVBfMiwgY29sb3IgPSBgMGApLCBzaGFwZT0xNiwgc2l6ZSA9IDMsIGFscGhhPTAuNSkgKyAKICBzY2FsZV9jb2xvcl9ncmFkaWVudCgnMCcsIGxvdyA9ICIjRkZGRkZGMDAiLCBoaWdoID0gIiM2ZGMwYTYiKSArCiAgbmV3X3NjYWxlKCJjb2xvciIpICsKICAgIGdlb21fcG9pbnQoZGF0YSA9IGVtYmVkZGluZ1tlbWJlZGRpbmckYDFgPjAuMSxdLCAKICAgICAgICAgICAgIGFlcyh4ID0gVU1BUF8xLCB5ID0gVU1BUF8yLCBjb2xvciA9IGAxYCksc2hhcGU9MTYsIHNpemUgPSAzLCBhbHBoYT0wLjUpICsgCiAgc2NhbGVfY29sb3JfZ3JhZGllbnQoJzEnLCBsb3cgPSAiI0ZGRkZGRjAwIiwgaGlnaCA9ICIjZTJiMzk4IikgKwogICBuZXdfc2NhbGUoImNvbG9yIikgKwogICAgZ2VvbV9wb2ludChkYXRhID0gZW1iZWRkaW5nW2VtYmVkZGluZyRgMmA+MC4xLF0sIAogICAgICAgICAgICAgYWVzKHggPSBVTUFQXzEsIHkgPSBVTUFQXzIsIGNvbG9yID0gYDJgKSxzaGFwZT0xNiwgc2l6ZSA9IDMsIGFscGhhPTAuNSkgKyAKICBzY2FsZV9jb2xvcl9ncmFkaWVudCgnMicsIGxvdyA9ICIjRkZGRkZGMDAiLCBoaWdoID0gIiNlMmEyY2EiKSArCiAgbmV3X3NjYWxlKCJjb2xvciIpICsKICAgIGdlb21fcG9pbnQoZGF0YSA9IGVtYmVkZGluZ1tlbWJlZGRpbmckYDNgPjAuMSxdLCAKICAgICAgICAgICAgIGFlcyh4ID0gVU1BUF8xLCB5ID0gVU1BUF8yLCBjb2xvciA9IGAzYCksc2hhcGU9MTYsIHNpemUgPSAzLCBhbHBoYT0wLjUpICsgCiAgc2NhbGVfY29sb3JfZ3JhZGllbnQoJzMnLCBsb3cgPSAiI0ZGRkZGRjAwIiwgaGlnaCA9ICIjZDFlYmE4IikgKwogICBuZXdfc2NhbGUoImNvbG9yIikgKwogICAgICBnZW9tX3BvaW50KGRhdGEgPSBlbWJlZGRpbmdbZW1iZWRkaW5nJGA0YD4wLjEsXSwgCiAgICAgICAgICAgICBhZXMoeCA9IFVNQVBfMSwgeSA9IFVNQVBfMiwgY29sb3IgPSBgNGApLHNoYXBlPTE2LCBzaXplID0gMywgYWxwaGE9MC41KSArIAogIHNjYWxlX2NvbG9yX2dyYWRpZW50KCc0JywgbG93ID0gIiNGRkZGRkYwMCIsIGhpZ2ggPSAiI2IxZDZmYiIpICsKICAgIG5ld19zY2FsZSgiY29sb3IiKSArCiAgICAgICAgeGxhYigiVU1BUCAxIikgKyB5bGFiKCJVTUFQIDIiKSAgKwogICAgICAgIHRoZW1lKGF4aXMubGluZSA9IGVsZW1lbnRfbGluZShhcnJvdyA9IGFycm93KGxlbmd0aCA9IHVuaXQoMC4yLCAiY20iKSkpKSArCiAgICAgICAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcyA9IE5VTEwpICsKICAgICAgICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gTlVMTCkgKyAKICB0aGVtZShwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLCBwYW5lbC5ncmlkID0gZWxlbWVudF9ibGFuaygpLCBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikKZ2dzYXZlKCJwcmVfZHMxX3VtYXAuc3ZnIixkZXZpY2UgPSBzdmcscGxvdCA9IGdnb2JqLGhlaWdodCA9IDEwLHdpZHRoID0gMTApCmBgYAoKIyMgZHMyIC0+IGRzMCAKIyMjIEFSSSA9IDAuNjY2NDY1NwpgYGB7cn0KSWRlbnRzKGRzMCkgPC0gZHMwJHNldXJhdF9jbHVzdGVycwp0ZW1wIDwtIGdldF9kYXRhX3RhYmxlKGRzMCwgaGlnaHZhciA9IEYsIHR5cGUgPSAiZGF0YSIpCmRzMF9kYXRhIDwtIG1hdHJpeChkYXRhPTAsIG5yb3cgPSBsZW5ndGgocm93bmFtZXMoZHMyX2RhdGEpKSwgbmNvbCA9IGxlbmd0aChjb2xuYW1lcyh0ZW1wKSksIAogICAgICAgICAgICAgICAgICAgYnlyb3cgPSBGQUxTRSwgZGltbmFtZXMgPSBsaXN0KHJvd25hbWVzKGRzMl9kYXRhKSxjb2xuYW1lcyh0ZW1wKSkpCmZvcihpIGluIGludGVyc2VjdChyb3duYW1lcyhkczJfZGF0YSksIHJvd25hbWVzKHRlbXApKSl7CiAgZHMwX2RhdGFbaSxdIDwtIHRlbXBbaSxdCn0Kcm0odGVtcCkKZHMwX2xhYmVsIDwtIGFzLm51bWVyaWMoYXMuY2hhcmFjdGVyKElkZW50cyhkczApKSkKY29sbmFtZXMoZHMwX2RhdGEpIDwtIE5VTEwKZHMwX3Rlc3RfZGF0YSA8LSBsaXN0KGRhdGEgPSB0KGFzKGRzMF9kYXRhLCJkZ0NNYXRyaXgiKSksIGxhYmVsID0gZHMwX2xhYmVsKQpkczBfdGVzdCA8LSB4Z2IuRE1hdHJpeChkYXRhID0gZHMwX3Rlc3RfZGF0YSRkYXRhLGxhYmVsID0gZHMwX3Rlc3RfZGF0YSRsYWJlbCkKCiPpooTmtYvnu5PmnpwKCnByZWRpY3RfZHMwX3Rlc3QgPC0gcHJlZGljdChic3RfbW9kZWwsIG5ld2RhdGEgPSBkczBfdGVzdCkKCnByZWRpY3RfcHJvcF9kczAgPC0gbWF0cml4KGRhdGE9cHJlZGljdF9kczBfdGVzdCwgbnJvdyA9IGxlbmd0aChsZXZlbHMoSWRlbnRzKGRzMikpKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG5jb2wgPSBuY29sKGRzMCksIGJ5cm93ID0gRkFMU0UsIAogICAgICAgICAgICAgICAgICAgICAgICAgICBkaW1uYW1lcyA9IGxpc3QobGV2ZWxzKElkZW50cyhkczIpKSxjb2xuYW1lcyhkczApKSkKCiMjIOW+l+WIsOWIhue+pOe7k+aenApkczBfcmVzIDwtIGFwcGx5KHByZWRpY3RfcHJvcF9kczAsMixmdW5jLHJvd25hbWVzKHByZWRpY3RfcHJvcF9kczApKQphZGp1c3RlZFJhbmRJbmRleChkczBfcmVzLCBkczBfdGVzdF9kYXRhJGxhYmVsKQpJZGVudHMoZHMwKSA8LSBmYWN0b3IoZHMwX3JlcyxsZXZlbHMgPSBjKDA6NCkpCnVtYXBwbG90KGRzMCkKZHMwJHN1cGNsdXN0ZXJpbmcgPC0gSWRlbnRzKGRzMCkgI+S/neWtmOebkeedo+iBmuexu+e7k+aenApgYGAKCmBgYHtyfQplbWJlZGRpbmcgPC0gRmV0Y2hEYXRhKG9iamVjdCA9IGRzMCwgdmFycyA9IGMoIlVNQVBfMSIsICJVTUFQXzIiKSkKZW1iZWRkaW5nIDwtIGNiaW5kKGVtYmVkZGluZywgdChwcmVkaWN0X3Byb3BfZHMwKSkKCmdnb2JqIDwtIGdncGxvdCgpICsKICBnZW9tX3BvaW50KGRhdGEgPSBlbWJlZGRpbmdbZW1iZWRkaW5nJGAwYD4wLjEsXSwgCiAgICAgICAgICAgICBhZXMoeCA9IFVNQVBfMSwgeSA9IFVNQVBfMiwgY29sb3IgPSBgMGApLCBzaGFwZT0xNiwgc2l6ZSA9IDMsIGFscGhhPTAuNSkgKyAKICBzY2FsZV9jb2xvcl9ncmFkaWVudCgnMCcsIGxvdyA9ICIjRkZGRkZGMDAiLCBoaWdoID0gIiM2ZGMwYTYiKSArCiAgbmV3X3NjYWxlKCJjb2xvciIpICsKICAgIGdlb21fcG9pbnQoZGF0YSA9IGVtYmVkZGluZ1tlbWJlZGRpbmckYDFgPjAuMSxdLCAKICAgICAgICAgICAgIGFlcyh4ID0gVU1BUF8xLCB5ID0gVU1BUF8yLCBjb2xvciA9IGAxYCksc2hhcGU9MTYsIHNpemUgPSAzLCBhbHBoYT0wLjUpICsgCiAgc2NhbGVfY29sb3JfZ3JhZGllbnQoJzEnLCBsb3cgPSAiI0ZGRkZGRjAwIiwgaGlnaCA9ICIjZTJiMzk4IikgKwogICBuZXdfc2NhbGUoImNvbG9yIikgKwogICAgZ2VvbV9wb2ludChkYXRhID0gZW1iZWRkaW5nW2VtYmVkZGluZyRgMmA+MC4xLF0sIAogICAgICAgICAgICAgYWVzKHggPSBVTUFQXzEsIHkgPSBVTUFQXzIsIGNvbG9yID0gYDJgKSxzaGFwZT0xNiwgc2l6ZSA9IDMsIGFscGhhPTAuNSkgKyAKICBzY2FsZV9jb2xvcl9ncmFkaWVudCgnMicsIGxvdyA9ICIjRkZGRkZGMDAiLCBoaWdoID0gIiNlMmEyY2EiKSArCiAgbmV3X3NjYWxlKCJjb2xvciIpICsKICAgIGdlb21fcG9pbnQoZGF0YSA9IGVtYmVkZGluZ1tlbWJlZGRpbmckYDNgPjAuMSxdLCAKICAgICAgICAgICAgIGFlcyh4ID0gVU1BUF8xLCB5ID0gVU1BUF8yLCBjb2xvciA9IGAzYCksc2hhcGU9MTYsIHNpemUgPSAzLCBhbHBoYT0wLjUpICsgCiAgc2NhbGVfY29sb3JfZ3JhZGllbnQoJzMnLCBsb3cgPSAiI0ZGRkZGRjAwIiwgaGlnaCA9ICIjZDFlYmE4IikgKwogICBuZXdfc2NhbGUoImNvbG9yIikgKwogICAgICBnZW9tX3BvaW50KGRhdGEgPSBlbWJlZGRpbmdbZW1iZWRkaW5nJGA0YD4wLjEsXSwgCiAgICAgICAgICAgICBhZXMoeCA9IFVNQVBfMSwgeSA9IFVNQVBfMiwgY29sb3IgPSBgNGApLHNoYXBlPTE2LCBzaXplID0gMywgYWxwaGE9MC41KSArIAogIHNjYWxlX2NvbG9yX2dyYWRpZW50KCc0JywgbG93ID0gIiNGRkZGRkYwMCIsIGhpZ2ggPSAiI2IxZDZmYiIpICsKICAgIG5ld19zY2FsZSgiY29sb3IiKSArCiAgICAgICAgeGxhYigiVU1BUCAxIikgKyB5bGFiKCJVTUFQIDIiKSAgKwogICAgICAgIHRoZW1lKGF4aXMubGluZSA9IGVsZW1lbnRfbGluZShhcnJvdyA9IGFycm93KGxlbmd0aCA9IHVuaXQoMC4yLCAiY20iKSkpKSArCiAgICAgICAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcyA9IE5VTEwpICsKICAgICAgICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gTlVMTCkgKyAKICB0aGVtZShwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLCBwYW5lbC5ncmlkID0gZWxlbWVudF9ibGFuaygpLCBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikKZ2dzYXZlKCJwcmVfZHMwX3VtYXAuc3ZnIixkZXZpY2UgPSBzdmcscGxvdCA9IGdnb2JqLGhlaWdodCA9IDEwLHdpZHRoID0gMTApCmBgYAoKCiMgUEEgLT4gQUMKYGBge3J9CklkZW50cyhkczJfUEEpIDwtIGRzMl9QQSRzZXVyYXRfY2x1c3RlcnMKc2VsZWN0ZWRfZmVhdHVyZXMgPC0gcmVhZC5jc3YoIi4vZGF0YXRhYmxlL3NlbGVjdGVkX2ZlYXR1cmVzLmNzdiIsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQpzZWxlY3RlZF9mZWF0dXJlcyA8LSBzZWxlY3RlZF9mZWF0dXJlcyR4ClBBX2RhdGEgPC0gZ2V0X2RhdGFfdGFibGUoZHMyX1BBLCBoaWdodmFyID0gRiwgdHlwZSA9ICJkYXRhIikKUEFfZGF0YSA8LSBQQV9kYXRhW3NlbGVjdGVkX2ZlYXR1cmVzLF0KUEFfbGFiZWwgPC0gYXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIoSWRlbnRzKGRzMl9QQSkpKQpjb2xuYW1lcyhQQV9kYXRhKSA8LSBOVUxMCgpQQV90cmFpbl9kYXRhIDwtIGxpc3QoZGF0YSA9IHQoYXMoUEFfZGF0YSwiZGdDTWF0cml4IikpLCBsYWJlbCA9IFBBX2xhYmVsKQpQQV90cmFpbiA8LSB4Z2IuRE1hdHJpeChkYXRhID0gUEFfdHJhaW5fZGF0YSRkYXRhLGxhYmVsID0gUEFfdHJhaW5fZGF0YSRsYWJlbCkKeGdiX3BhcmFtIDwtIGxpc3QoZXRhID0gMC4yLCBtYXhfZGVwdGggPSA2LCAKICAgICAgICAgICAgICAgICAgc3Vic2FtcGxlID0gMC42LCAgbnVtX2NsYXNzID0gbGVuZ3RoKHRhYmxlKElkZW50cyhkczJfUEEpKSksCiAgICAgICAgICAgICAgICAgIG9iamVjdGl2ZSA9ICJtdWx0aTpzb2Z0cHJvYiIsIGV2YWxfbWV0cmljID0gJ21sb2dsb3NzJykKCmJzdF9tb2RlbCA8LSB4Z2IudHJhaW4oeGdiX3BhcmFtLCBQQV90cmFpbiwgbnJvdW5kcyA9IDEwMCwgdmVyYm9zZSA9IDApCmBgYAoKYGBge3J9CklkZW50cyhkczJfQUMpIDwtIGRzMl9BQyRzZXVyYXRfY2x1c3RlcnMKQUNfZGF0YSA8LSBnZXRfZGF0YV90YWJsZShkczJfQUMsIGhpZ2h2YXIgPSBGLCB0eXBlID0gImRhdGEiKQpBQ19kYXRhIDwtIEFDX2RhdGFbc2VsZWN0ZWRfZmVhdHVyZXMsXQpBQ19sYWJlbCA8LSBhcy5udW1lcmljKGFzLmNoYXJhY3RlcihJZGVudHMoZHMyX0FDKSkpCmNvbG5hbWVzKEFDX2RhdGEpIDwtIE5VTEwKQUNfdGVzdF9kYXRhIDwtIGxpc3QoZGF0YSA9IHQoYXMoQUNfZGF0YSwiZGdDTWF0cml4IikpLCBsYWJlbCA9IEFDX2xhYmVsKQpBQ190ZXN0IDwtIHhnYi5ETWF0cml4KGRhdGEgPSBBQ190ZXN0X2RhdGEkZGF0YSxsYWJlbCA9IEFDX3Rlc3RfZGF0YSRsYWJlbCkKCiPpooTmtYvnu5PmnpwKcHJlZGljdF9wcm9wX0FDIDwtcHJlZGljdChic3RfbW9kZWwsIG5ld2RhdGEgPSBBQ190ZXN0KSAlPiUKIG1hdHJpeChucm93ID0gbGVuZ3RoKGxldmVscyhJZGVudHMoZHMyX1BBKSkpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgbmNvbCA9IG5jb2woZHMyX0FDKSwgYnlyb3cgPSBGQUxTRSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGRpbW5hbWVzID0gbGlzdChsZXZlbHMoSWRlbnRzKGRzMl9QQSkpLGNvbG5hbWVzKGRzMl9BQykpKQpBQ19yZXMgPC0gYXBwbHkocHJlZGljdF9wcm9wX0FDLDIsZnVuYyxyb3duYW1lcyhwcmVkaWN0X3Byb3BfQUMpKQoKY29uZnVzZV9tYXRyaXgxIDwtIHRhYmxlKEFDX3Rlc3RfZGF0YSRsYWJlbCwgQUNfcmVzLCBkbm49YygidHJ1ZSIsInByZSIpKQpzYW5rZXlfcGxvdChjb25mdXNlX21hdHJpeDEsc2Vzc2lvbiA9ICJQQXRvQUMiKQoKSWRlbnRzKGRzMl9BQykgPC0gZmFjdG9yKEFDX3JlcyxsZXZlbHMgPSBjKDA6MikpCnVtYXBwbG90KGRzMl9BQykKYGBgCgpgYGB7cn0KZW1iZWRkaW5nIDwtIEZldGNoRGF0YShvYmplY3QgPSBkczJfQUMsIHZhcnMgPSBjKCJVTUFQXzEiLCAiVU1BUF8yIikpCmVtYmVkZGluZyA8LSBjYmluZChlbWJlZGRpbmcsIHQocHJlZGljdF9wcm9wX0FDKSkKCmdnb2JqIDwtIGdncGxvdCgpICsKICBnZW9tX3BvaW50KGRhdGEgPSBlbWJlZGRpbmdbZW1iZWRkaW5nJGAwYD4wLjEsXSwgCiAgICAgICAgICAgICBhZXMoeCA9IFVNQVBfMSwgeSA9IFVNQVBfMiwgY29sb3IgPSBgMGApLCBzaGFwZT0xNiwgc2l6ZSA9IDIsIGFscGhhPTAuNSkgKyAKICBzY2FsZV9jb2xvcl9ncmFkaWVudCgnMCcsIGxvdyA9ICIjRkZGRkZGMDAiLCBoaWdoID0gIiM2ZGMwYTYiKSArCiAgbmV3X3NjYWxlKCJjb2xvciIpICsKICAgIGdlb21fcG9pbnQoZGF0YSA9IGVtYmVkZGluZ1tlbWJlZGRpbmckYDFgPjAuMSxdLCAKICAgICAgICAgICAgIGFlcyh4ID0gVU1BUF8xLCB5ID0gVU1BUF8yLCBjb2xvciA9IGAxYCksc2hhcGU9MTYsIHNpemUgPSAyLCBhbHBoYT0wLjUpICsgCiAgc2NhbGVfY29sb3JfZ3JhZGllbnQoJzEnLCBsb3cgPSAiI0ZGRkZGRjAwIiwgaGlnaCA9ICIjZTJiMzk4IikgKwogICBuZXdfc2NhbGUoImNvbG9yIikgKwogICAgZ2VvbV9wb2ludChkYXRhID0gZW1iZWRkaW5nW2VtYmVkZGluZyRgMmA+MC4xLF0sIAogICAgICAgICAgICAgYWVzKHggPSBVTUFQXzEsIHkgPSBVTUFQXzIsIGNvbG9yID0gYDJgKSxzaGFwZT0xNiwgc2l6ZSA9IDIsIGFscGhhPTAuNSkgKyAKICBzY2FsZV9jb2xvcl9ncmFkaWVudCgnMicsIGxvdyA9ICIjRkZGRkZGMDAiLCBoaWdoID0gIiNlMmEyY2EiKSArCiAgICAgICAgeGxhYigiVU1BUCAxIikgKyB5bGFiKCJVTUFQIDIiKSAgKwogICAgICAgIHRoZW1lKGF4aXMubGluZSA9IGVsZW1lbnRfbGluZShhcnJvdyA9IGFycm93KGxlbmd0aCA9IHVuaXQoMC4yLCAiY20iKSkpKSArCiAgICAgICAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcyA9IE5VTEwpICsKICAgICAgICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gTlVMTCkgKyAKICB0aGVtZShwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLCBwYW5lbC5ncmlkID0gZWxlbWVudF9ibGFuaygpLCBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikKZ2dzYXZlKCJkczJfUEF0b0FDX3VtYXAuc3ZnIixkZXZpY2UgPSBzdmcscGxvdCA9IGdnb2JqLGhlaWdodCA9IDgsd2lkdGggPSA4KQpgYGAKCgoKIyMgQUMgdG8gUEEKYGBge3J9CklkZW50cyhkczJfQUMpIDwtIGRzMl9BQyRzZXVyYXRfY2x1c3RlcnMKc2VsZWN0ZWRfZmVhdHVyZXMgPC0gcmVhZC5jc3YoIi4vZGF0YXRhYmxlL3NlbGVjdGVkX2ZlYXR1cmVzLmNzdiIsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQpzZWxlY3RlZF9mZWF0dXJlcyA8LSBzZWxlY3RlZF9mZWF0dXJlcyR4CkFDX2RhdGEgPC0gZ2V0X2RhdGFfdGFibGUoZHMyX0FDLCBoaWdodmFyID0gRiwgdHlwZSA9ICJkYXRhIikKQUNfZGF0YSA8LSBBQ19kYXRhW3NlbGVjdGVkX2ZlYXR1cmVzLF0KQUNfbGFiZWwgPC0gYXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIoSWRlbnRzKGRzMl9BQykpKQpjb2xuYW1lcyhBQ19kYXRhKSA8LSBOVUxMCgpBQ190cmFpbl9kYXRhIDwtIGxpc3QoZGF0YSA9IHQoYXMoQUNfZGF0YSwiZGdDTWF0cml4IikpLCBsYWJlbCA9IEFDX2xhYmVsKQpBQ190cmFpbiA8LSB4Z2IuRE1hdHJpeChkYXRhID0gQUNfdHJhaW5fZGF0YSRkYXRhLGxhYmVsID0gQUNfdHJhaW5fZGF0YSRsYWJlbCkKeGdiX0FDcmFtIDwtIGxpc3QoZXRhID0gMC4yLCBtYXhfZGVwdGggPSA2LCAKICAgICAgICAgICAgICAgICAgc3Vic2FtcGxlID0gMC42LCAgbnVtX2NsYXNzID0gbGVuZ3RoKHRhYmxlKElkZW50cyhkczJfQUMpKSksCiAgICAgICAgICAgICAgICAgIG9iamVjdGl2ZSA9ICJtdWx0aTpzb2Z0cHJvYiIsIGV2YWxfbWV0cmljID0gJ21sb2dsb3NzJykKCmJzdF9tb2RlbCA8LSB4Z2IudHJhaW4oeGdiX0FDcmFtLCBBQ190cmFpbiwgbnJvdW5kcyA9IDEwMCwgdmVyYm9zZSA9IDApCmBgYAoKYGBge3J9CklkZW50cyhkczJfUEEpIDwtIGZhY3RvcihkczJfUEEkc2V1cmF0X2NsdXN0ZXJzLGxldmVscyA9IGMoMCwxLDIpKQoKUEFfZGF0YSA8LSBnZXRfZGF0YV90YWJsZShkczJfUEEsIGhpZ2h2YXIgPSBGLCB0eXBlID0gImRhdGEiKQpQQV9kYXRhIDwtIFBBX2RhdGFbc2VsZWN0ZWRfZmVhdHVyZXMsXQpQQV9sYWJlbCA8LSBhcy5udW1lcmljKGFzLmNoYXJhY3RlcihJZGVudHMoZHMyX1BBKSkpCmNvbG5hbWVzKFBBX2RhdGEpIDwtIE5VTEwKUEFfdGVzdF9kYXRhIDwtIGxpc3QoZGF0YSA9IHQoYXMoUEFfZGF0YSwiZGdDTWF0cml4IikpLCBsYWJlbCA9IFBBX2xhYmVsKQpQQV90ZXN0IDwtIHhnYi5ETWF0cml4KGRhdGEgPSBQQV90ZXN0X2RhdGEkZGF0YSxsYWJlbCA9IFBBX3Rlc3RfZGF0YSRsYWJlbCkKCiPpooTmtYvnu5PmnpwKcHJlZGljdF9wcm9wX1BBIDwtcHJlZGljdChic3RfbW9kZWwsIG5ld2RhdGEgPSBQQV90ZXN0KSAlPiUKIG1hdHJpeChucm93ID0gbGVuZ3RoKGxldmVscyhJZGVudHMoZHMyX0FDKSkpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgbmNvbCA9IG5jb2woZHMyX1BBKSwgYnlyb3cgPSBGQUxTRSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGRpbW5hbWVzID0gbGlzdChsZXZlbHMoSWRlbnRzKGRzMl9BQykpLGNvbG5hbWVzKGRzMl9QQSkpKQpQQV9yZXMgPC0gYXBwbHkocHJlZGljdF9wcm9wX1BBLDIsZnVuYyxyb3duYW1lcyhwcmVkaWN0X3Byb3BfUEEpKQoKY29uZnVzZV9tYXRyaXgxIDwtIHRhYmxlKFBBX3Rlc3RfZGF0YSRsYWJlbCwgUEFfcmVzLCBkbm49YygidHJ1ZSIsInByZSIpKQpzYW5rZXlfcGxvdChjb25mdXNlX21hdHJpeDEsc2Vzc2lvbiA9ICJBQ3RvUEEiKQoKSWRlbnRzKGRzMl9QQSkgPC0gZmFjdG9yKFBBX3JlcykKdW1hcHBsb3QoZHMyX1BBKQpgYGAKCmBgYHtyfQplbWJlZGRpbmcgPC0gRmV0Y2hEYXRhKG9iamVjdCA9IGRzMl9QQSwgdmFycyA9IGMoIlVNQVBfMSIsICJVTUFQXzIiKSkKZW1iZWRkaW5nIDwtIGNiaW5kKGVtYmVkZGluZywgdChwcmVkaWN0X3Byb3BfUEEpKQoKZ2dvYmogPC0gZ2dwbG90KCkgKwogIGdlb21fcG9pbnQoZGF0YSA9IGVtYmVkZGluZ1tlbWJlZGRpbmckYDBgPjAuMSxdLCAKICAgICAgICAgICAgIGFlcyh4ID0gVU1BUF8xLCB5ID0gVU1BUF8yLCBjb2xvciA9IGAwYCksIHNoYXBlPTE2LCBzaXplID0gMiwgYWxwaGE9MC41KSArIAogIHNjYWxlX2NvbG9yX2dyYWRpZW50KCcwJywgbG93ID0gIiNGRkZGRkYwMCIsIGhpZ2ggPSAiIzZkYzBhNiIpICsKICBuZXdfc2NhbGUoImNvbG9yIikgKwogICAgZ2VvbV9wb2ludChkYXRhID0gZW1iZWRkaW5nW2VtYmVkZGluZyRgMWA+MC4xLF0sIAogICAgICAgICAgICAgYWVzKHggPSBVTUFQXzEsIHkgPSBVTUFQXzIsIGNvbG9yID0gYDFgKSxzaGFwZT0xNiwgc2l6ZSA9IDIsIGFscGhhPTAuNSkgKyAKICBzY2FsZV9jb2xvcl9ncmFkaWVudCgnMScsIGxvdyA9ICIjRkZGRkZGMDAiLCBoaWdoID0gIiNlMmIzOTgiKSArCiAgIG5ld19zY2FsZSgiY29sb3IiKSArCiAgICBnZW9tX3BvaW50KGRhdGEgPSBlbWJlZGRpbmdbZW1iZWRkaW5nJGAyYD4wLjEsXSwgCiAgICAgICAgICAgICBhZXMoeCA9IFVNQVBfMSwgeSA9IFVNQVBfMiwgY29sb3IgPSBgMmApLHNoYXBlPTE2LCBzaXplID0gMiwgYWxwaGE9MC41KSArIAogIHNjYWxlX2NvbG9yX2dyYWRpZW50KCcyJywgbG93ID0gIiNGRkZGRkYwMCIsIGhpZ2ggPSAiI2UyYTJjYSIpICsKICAgICBuZXdfc2NhbGUoImNvbG9yIikgKwogICAgZ2VvbV9wb2ludChkYXRhID0gZW1iZWRkaW5nW2VtYmVkZGluZyRgM2A+MC4xLF0sIAogICAgICAgICAgICAgYWVzKHggPSBVTUFQXzEsIHkgPSBVTUFQXzIsIGNvbG9yID0gYDNgKSxzaGFwZT0xNiwgc2l6ZSA9IDIsIGFscGhhPTAuNSkgKyAKICBzY2FsZV9jb2xvcl9ncmFkaWVudCgnMycsIGxvdyA9ICIjRkZGRkZGMDAiLCBoaWdoID0gIiNkMWViYTgiKSArCiAgICAgICAgeGxhYigiVU1BUCAxIikgKyB5bGFiKCJVTUFQIDIiKSAgKwogICAgICAgIHRoZW1lKGF4aXMubGluZSA9IGVsZW1lbnRfbGluZShhcnJvdyA9IGFycm93KGxlbmd0aCA9IHVuaXQoMC4yLCAiY20iKSkpKSArCiAgICAgICAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcyA9IE5VTEwpICsKICAgICAgICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gTlVMTCkgKyAKICB0aGVtZShwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLCBwYW5lbC5ncmlkID0gZWxlbWVudF9ibGFuaygpLCBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikKZ2dzYXZlKCJkczJfQUN0b1BBX3VtYXAuc3ZnIixkZXZpY2UgPSBzdmcscGxvdCA9IGdnb2JqLGhlaWdodCA9IDgsd2lkdGggPSA4KQpgYGAKCgojIOWcqGRzMOS4iuiuree7gwpgYGB7cn0KSWRlbnRzKGRzMCkgPC0gZHMwJHNldXJhdF9jbHVzdGVycwpkczBfZGF0YSA8LSBnZXRfZGF0YV90YWJsZShkczAsIGhpZ2h2YXIgPSBGLCB0eXBlID0gImRhdGEiKQpkczBfbGFiZWwgPC0gYXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIoSWRlbnRzKGRzMCkpKQoKaW5kZXggPC0gYygxOmRpbShkczBfZGF0YSlbMl0pICU+JSBzYW1wbGUoY2VpbGluZygwLjMqZGltKGRzMF9kYXRhKVsyXSksIHJlcGxhY2UgPSBGLCBwcm9iID0gTlVMTCkKY29sbmFtZXMoZHMwX2RhdGEpIDwtIE5VTEwKCmRzMF90cmFpbl9kYXRhIDwtIGxpc3QoZGF0YSA9IHQoYXMoZHMwX2RhdGFbLC1pbmRleF0sImRnQ01hdHJpeCIpKSwgbGFiZWwgPSBkczBfbGFiZWxbLWluZGV4XSkKZHMwX3Rlc3RfZGF0YSA8LSBsaXN0KGRhdGEgPSB0KGFzKGRzMF9kYXRhWyxpbmRleF0sImRnQ01hdHJpeCIpKSwgbGFiZWwgPSBkczBfbGFiZWxbaW5kZXhdKQoKZHMwX3RyYWluIDwtIHhnYi5ETWF0cml4KGRhdGEgPSBkczBfdHJhaW5fZGF0YSRkYXRhLGxhYmVsID0gZHMwX3RyYWluX2RhdGEkbGFiZWwpCmRzMF90ZXN0IDwtIHhnYi5ETWF0cml4KGRhdGEgPSBkczBfdGVzdF9kYXRhJGRhdGEsbGFiZWwgPSBkczBfdGVzdF9kYXRhJGxhYmVsKQoKd2F0Y2hsaXN0IDwtIGxpc3QodHJhaW4gPSBkczBfdHJhaW4sIGV2YWwgPSBkczBfdGVzdCkKeGdiX3BhcmFtIDwtIGxpc3QoZXRhID0gMC4yLCBtYXhfZGVwdGggPSA2LCAKICAgICAgICAgICAgICAgICAgc3Vic2FtcGxlID0gMC42LCAgbnVtX2NsYXNzID0gbGVuZ3RoKHRhYmxlKElkZW50cyhkczApKSksCiAgICAgICAgICAgICAgICAgIG9iamVjdGl2ZSA9ICJtdWx0aTpzb2Z0cHJvYiIsIGV2YWxfbWV0cmljID0gJ21sb2dsb3NzJykKCmJzdF9tb2RlbCA8LSB4Z2IudHJhaW4oeGdiX3BhcmFtLCBkczBfdHJhaW4sIG5yb3VuZHMgPSAxMDAsIHdhdGNobGlzdCwgdmVyYm9zZSA9IDApCnNhdmVSRFMoYnN0X21vZGVsLCAiZHMwX21vZGVsLnJkcyIpCmV2YWxfbG9zcyA8LSBic3RfbW9kZWxbWyJldmFsdWF0aW9uX2xvZyJdXVtbImV2YWxfbWxvZ2xvc3MiXV0KcGxvdF9seShkYXRhLmZyYW1lKGV2YWxfbG9zcyksIHggPSBjKDE6MTAwKSwgeSA9IGV2YWxfbG9zcykgJT4lIAogIGFkZF90cmFjZSh0eXBlID0gInNjYXR0ZXIiLCBtb2RlID0gIm1hcmtlcnMrbGluZXMiLCAKICAgICAgICAgICAgbWFya2VyID0gbGlzdChjb2xvciA9ICJibGFjayIsIGxpbmUgPSBsaXN0KGNvbG9yID0gIiMxRTkwRkZDNyIsIHdpZHRoID0gMSkpLAogICAgICAgICAgICBsaW5lID0gbGlzdChjb2xvciA9ICIjMUU5MEZGODAiLCB3aWR0aCA9IDIpKSAlPiUgCiAgbGF5b3V0KHhheGlzID0gbGlzdCh0aXRsZSA9ICJlcG9jaCIpLHlheGlzID0gbGlzdCh0aXRsZSA9ICJldmFsX21sb2dsb3NzIikpCmBgYAoKYGBge3IgZmlnLndpZHRoPTYsZmlnLmhlaWdodD02fQppbXBvcnRhbmNlIDwtIHhnYi5pbXBvcnRhbmNlKGNvbG5hbWVzKGRzMF90cmFpbiksIG1vZGVsID0gYnN0X21vZGVsKQpoZWFkKGltcG9ydGFuY2UpCnhnYi5nZ3Bsb3QuaW1wb3J0YW5jZShoZWFkKGltcG9ydGFuY2UsMjApLG5fY2x1c3RlcnMgPSAxKSArIHRoZW1lX2J3KCkrdGhlbWUoCiAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE1KSwgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDgsIGNvbG91ciA9ICJibGFjayIpLAogICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNSksIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgY29sb3VyID0gImJsYWNrIiksCiAgICBsZWdlbmQudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMjApLCBsZWdlbmQudGl0bGUgPSBlbGVtZW50X2JsYW5rKCksIHBhbmVsLmdyaWQgPSBlbGVtZW50X2JsYW5rKCkpCndyaXRlLmNzdihpbXBvcnRhbmNlLCAiLi9kYXRhdGFibGUvZHMwX2ZlYXR1cmVzLmNzdiIsIHJvdy5uYW1lcyA9IEYpCm11bHRpX2ZlYXR1cmVwbG90KGhlYWQoaW1wb3J0YW5jZSw5KSRGZWF0dXJlLCBkczAsIGxhYmVscyA9ICIiKSAKYGBgCiMjIGRzMCAtPiBkczIKIyMjIEFSSSA9IDAuNDAxNTAwMgpgYGB7cn0KSWRlbnRzKGRzMikgPC0gZHMyJHNldXJhdF9jbHVzdGVycyAKdGVtcCA8LSBnZXRfZGF0YV90YWJsZShkczIsIGhpZ2h2YXIgPSBGLCB0eXBlID0gImRhdGEiKQpkczJfZGF0YSA8LSBtYXRyaXgoZGF0YT0wLCBucm93ID0gbGVuZ3RoKHJvd25hbWVzKGRzMF9kYXRhKSksIG5jb2wgPSBsZW5ndGgoY29sbmFtZXModGVtcCkpLCAKICAgICAgICAgICAgICAgICAgIGJ5cm93ID0gRkFMU0UsIGRpbW5hbWVzID0gbGlzdChyb3duYW1lcyhkczBfZGF0YSksY29sbmFtZXModGVtcCkpKQpmb3IoaSBpbiBpbnRlcnNlY3Qocm93bmFtZXMoZHMyX2RhdGEpLCByb3duYW1lcyh0ZW1wKSkpewogIGRzMl9kYXRhW2ksXSA8LSB0ZW1wW2ksXQp9CnJtKHRlbXApCmRzMl9sYWJlbCA8LSBhcy5udW1lcmljKGFzLmNoYXJhY3RlcihJZGVudHMoZHMyKSkpCmNvbG5hbWVzKGRzMl9kYXRhKSA8LSBOVUxMCmRzMl90ZXN0X2RhdGEgPC0gbGlzdChkYXRhID0gdChhcyhkczJfZGF0YSwiZGdDTWF0cml4IikpLCBsYWJlbCA9IGRzMl9sYWJlbCkKZHMyX3Rlc3QgPC0geGdiLkRNYXRyaXgoZGF0YSA9IGRzMl90ZXN0X2RhdGEkZGF0YSxsYWJlbCA9IGRzMl90ZXN0X2RhdGEkbGFiZWwpCgoj6aKE5rWL57uT5p6cCgpwcmVkaWN0X2RzMl90ZXN0IDwtIHByZWRpY3QoYnN0X21vZGVsLCBuZXdkYXRhID0gZHMyX3Rlc3QpCgpwcmVkaWN0X3Byb3BfZHMyIDwtIG1hdHJpeChkYXRhPXByZWRpY3RfZHMyX3Rlc3QsIG5yb3cgPSBic3RfbW9kZWxbWyJwYXJhbXMiXV1bWyJudW1fY2xhc3MiXV0sIAogICAgICAgICAgICAgICAgICAgICAgICAgICBuY29sID0gbmNvbChkczIpLCBieXJvdyA9IEZBTFNFLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgZGltbmFtZXMgPSBsaXN0KGMoMDooYnN0X21vZGVsW1sicGFyYW1zIl1dW1sibnVtX2NsYXNzIl1dLTEpKSxjb2xuYW1lcyhkczIpKSkKCiMjIOW+l+WIsOWIhue+pOe7k+aenApkczJfcmVzIDwtIGFwcGx5KHByZWRpY3RfcHJvcF9kczIsMixmdW5jLHJvd25hbWVzKHByZWRpY3RfcHJvcF9kczIpKQoKYWRqdXN0ZWRSYW5kSW5kZXgoZHMyX3JlcywgZHMyX3Rlc3RfZGF0YSRsYWJlbCkKY29uZnVzZV9tYXRyaXgxIDwtIHRhYmxlKGRzMl90ZXN0X2RhdGEkbGFiZWwsIGRzMl9yZXMsIGRubj1jKCJ0cnVlIiwicHJlIikpCgpzYW5rZXlfcGxvdChjb25mdXNlX21hdHJpeDEsMDo1LDA6NCxzZXNzaW9uID0gImRzMHRvZHMyIikKCklkZW50cyhkczIpIDwtIGZhY3RvcihkczJfcmVzLGxldmVscyA9IGMoMDo1KSkKdW1hcHBsb3QoZHMyKQoKYGBgCgpgYGB7cn0KZW1iZWRkaW5nIDwtIEZldGNoRGF0YShvYmplY3QgPSBkczIsIHZhcnMgPSBjKCJVTUFQXzEiLCAiVU1BUF8yIikpCmVtYmVkZGluZyA8LSBjYmluZChlbWJlZGRpbmcsIHQocHJlZGljdF9wcm9wX2RzMikpCgpnZ29iaiA8LSBnZ3Bsb3QoKSArCiAgZ2VvbV9wb2ludChkYXRhID0gZW1iZWRkaW5nW2VtYmVkZGluZyRgMGA+MC4xLF0sIAogICAgICAgICAgICAgYWVzKHggPSBVTUFQXzEsIHkgPSBVTUFQXzIsIGNvbG9yID0gYDBgKSwgc2hhcGU9MTYsIHNpemUgPSAyLCBhbHBoYT0wLjUpICsgCiAgc2NhbGVfY29sb3JfZ3JhZGllbnQoJzAnLCBsb3cgPSAiI0ZGRkZGRjAwIiwgaGlnaCA9ICIjNmRjMGE2IikgKwogIG5ld19zY2FsZSgiY29sb3IiKSArCiAgICBnZW9tX3BvaW50KGRhdGEgPSBlbWJlZGRpbmdbZW1iZWRkaW5nJGAxYD4wLjEsXSwgCiAgICAgICAgICAgICBhZXMoeCA9IFVNQVBfMSwgeSA9IFVNQVBfMiwgY29sb3IgPSBgMWApLHNoYXBlPTE2LCBzaXplID0gMiwgYWxwaGE9MC41KSArIAogIHNjYWxlX2NvbG9yX2dyYWRpZW50KCcxJywgbG93ID0gIiNGRkZGRkYwMCIsIGhpZ2ggPSAiI2UyYjM5OCIpICsKICAgbmV3X3NjYWxlKCJjb2xvciIpICsKICAgIGdlb21fcG9pbnQoZGF0YSA9IGVtYmVkZGluZ1tlbWJlZGRpbmckYDJgPjAuMSxdLCAKICAgICAgICAgICAgIGFlcyh4ID0gVU1BUF8xLCB5ID0gVU1BUF8yLCBjb2xvciA9IGAyYCksc2hhcGU9MTYsIHNpemUgPSAyLCBhbHBoYT0wLjUpICsgCiAgc2NhbGVfY29sb3JfZ3JhZGllbnQoJzInLCBsb3cgPSAiI0ZGRkZGRjAwIiwgaGlnaCA9ICIjZTJhMmNhIikgKwogICAgIG5ld19zY2FsZSgiY29sb3IiKSArCiAgICBnZW9tX3BvaW50KGRhdGEgPSBlbWJlZGRpbmdbZW1iZWRkaW5nJGAzYD4wLjEsXSwgCiAgICAgICAgICAgICBhZXMoeCA9IFVNQVBfMSwgeSA9IFVNQVBfMiwgY29sb3IgPSBgM2ApLHNoYXBlPTE2LCBzaXplID0gMiwgYWxwaGE9MC41KSArIAogIHNjYWxlX2NvbG9yX2dyYWRpZW50KCczJywgbG93ID0gIiNGRkZGRkYwMCIsIGhpZ2ggPSAiI2QxZWJhOCIpICsKICAgICBuZXdfc2NhbGUoImNvbG9yIikgKwogICAgZ2VvbV9wb2ludChkYXRhID0gZW1iZWRkaW5nW2VtYmVkZGluZyRgNGA+MC4xLF0sIAogICAgICAgICAgICAgYWVzKHggPSBVTUFQXzEsIHkgPSBVTUFQXzIsIGNvbG9yID0gYDRgKSxzaGFwZT0xNiwgc2l6ZSA9IDIsIGFscGhhPTAuNSkgKyAKICBzY2FsZV9jb2xvcl9ncmFkaWVudCgnNCcsIGxvdyA9ICIjRkZGRkZGMDAiLCBoaWdoID0gIiNiMWQ2ZmIiKSArCiAgICAgbmV3X3NjYWxlKCJjb2xvciIpICsKICAgIGdlb21fcG9pbnQoZGF0YSA9IGVtYmVkZGluZ1tlbWJlZGRpbmckYDVgPjAuMSxdLCAKICAgICAgICAgICAgIGFlcyh4ID0gVU1BUF8xLCB5ID0gVU1BUF8yLCBjb2xvciA9IGA1YCksc2hhcGU9MTYsIHNpemUgPSAyLCBhbHBoYT0wLjUpICsgCiAgc2NhbGVfY29sb3JfZ3JhZGllbnQoJzUnLCBsb3cgPSAiI0ZGRkZGRjAwIiwgaGlnaCA9ICIjZmQ5OTk5IikgKwogICAgICAgIHhsYWIoIlVNQVAgMSIpICsgeWxhYigiVU1BUCAyIikgICsKICAgICAgICB0aGVtZShheGlzLmxpbmUgPSBlbGVtZW50X2xpbmUoYXJyb3cgPSBhcnJvdyhsZW5ndGggPSB1bml0KDAuMiwgImNtIikpKSkgKwogICAgICAgIHNjYWxlX3lfY29udGludW91cyhicmVha3MgPSBOVUxMKSArCiAgICAgICAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IE5VTEwpICsgCiAgdGhlbWUocGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwgcGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKSwgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpCmdnc2F2ZSgiZHMwdG9kczJ1bWFwLnN2ZyIsZGV2aWNlID0gc3ZnLHBsb3QgPSBnZ29iaixoZWlnaHQgPSA4LHdpZHRoID0gOCkKYGBgCgojIyBkczAgLT4gZHMxCiMjIyBBUkkgPSAwLjI1MjQyMzgKYGBge3J9CklkZW50cyhkczEpIDwtIGRzMSRzZXVyYXRfY2x1c3RlcnMKdGVtcCA8LSBnZXRfZGF0YV90YWJsZShkczEsIGhpZ2h2YXIgPSBGLCB0eXBlID0gImRhdGEiKQpkczFfZGF0YSA8LSBtYXRyaXgoZGF0YT0wLCBucm93ID0gbGVuZ3RoKHJvd25hbWVzKGRzMF9kYXRhKSksIG5jb2wgPSBsZW5ndGgoY29sbmFtZXModGVtcCkpLCAKICAgICAgICAgICAgICAgICAgIGJ5cm93ID0gRkFMU0UsIGRpbW5hbWVzID0gbGlzdChyb3duYW1lcyhkczBfZGF0YSksY29sbmFtZXModGVtcCkpKQpmb3IoaSBpbiBpbnRlcnNlY3Qocm93bmFtZXMoZHMxX2RhdGEpLCByb3duYW1lcyh0ZW1wKSkpewogIGRzMV9kYXRhW2ksXSA8LSB0ZW1wW2ksXQp9CnJtKHRlbXApCmRzMV9sYWJlbCA8LSBhcy5udW1lcmljKGFzLmNoYXJhY3RlcihJZGVudHMoZHMxKSkpCmNvbG5hbWVzKGRzMV9kYXRhKSA8LSBOVUxMCmRzMV90ZXN0X2RhdGEgPC0gbGlzdChkYXRhID0gdChhcyhkczFfZGF0YSwiZGdDTWF0cml4IikpLCBsYWJlbCA9IGRzMV9sYWJlbCkKZHMxX3Rlc3QgPC0geGdiLkRNYXRyaXgoZGF0YSA9IGRzMV90ZXN0X2RhdGEkZGF0YSxsYWJlbCA9IGRzMV90ZXN0X2RhdGEkbGFiZWwpCgoj6aKE5rWL57uT5p6cCgpwcmVkaWN0X2RzMV90ZXN0IDwtIHByZWRpY3QoYnN0X21vZGVsLCBuZXdkYXRhID0gZHMxX3Rlc3QpCgpwcmVkaWN0X3Byb3BfZHMxIDwtIG1hdHJpeChkYXRhPXByZWRpY3RfZHMxX3Rlc3QsIG5yb3cgPSBic3RfbW9kZWxbWyJwYXJhbXMiXV1bWyJudW1fY2xhc3MiXV0sIAogICAgICAgICAgICAgICAgICAgICAgICAgICBuY29sID0gbmNvbChkczEpLCBieXJvdyA9IEZBTFNFLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgZGltbmFtZXMgPSBsaXN0KGMoMDooYnN0X21vZGVsW1sicGFyYW1zIl1dW1sibnVtX2NsYXNzIl1dLTEpKSxjb2xuYW1lcyhkczEpKSkKCiMjIOW+l+WIsOWIhue+pOe7k+aenApkczFfcmVzIDwtIGFwcGx5KHByZWRpY3RfcHJvcF9kczEsMixmdW5jLHJvd25hbWVzKHByZWRpY3RfcHJvcF9kczEpKQphZGp1c3RlZFJhbmRJbmRleChkczFfdGVzdF9kYXRhJGxhYmVsLCBkczFfcmVzKQpJZGVudHMoZHMxKSA8LSBmYWN0b3IoZHMxX3JlcyxsZXZlbHMgPSBjKDA6NSkpCnVtYXBwbG90KGRzMSkKIyB1bWFwcGxvdChkczEsZ3JvdXAuYnkgPSAiQ2xhc3NpZmljYXRpb24xIikKY29uZnVzZV9tYXRyaXggPC0gdGFibGUoZHMxX3Rlc3RfZGF0YSRsYWJlbCwgZHMxX3JlcywgZG5uPWMoInRydWUiLCJwcmUiKSkKc2Fua2V5X3Bsb3QoY29uZnVzZV9tYXRyaXgsYygwOjQpLGMoMDo0KSxzZXNzaW9uID0gImRzMHRvZHMxIikKYGBgCgpgYGB7cn0KZW1iZWRkaW5nIDwtIEZldGNoRGF0YShvYmplY3QgPSBkczEsIHZhcnMgPSBjKCJVTUFQXzEiLCAiVU1BUF8yIikpCmVtYmVkZGluZyA8LSBjYmluZChlbWJlZGRpbmcsIHQocHJlZGljdF9wcm9wX2RzMSkpCgpnZ29iaiA8LSBnZ3Bsb3QoKSArCiAgZ2VvbV9wb2ludChkYXRhID0gZW1iZWRkaW5nW2VtYmVkZGluZyRgMGA+MC4xLF0sIAogICAgICAgICAgICAgYWVzKHggPSBVTUFQXzEsIHkgPSBVTUFQXzIsIGNvbG9yID0gYDBgKSwgc2hhcGU9MTYsIHNpemUgPSAyLCBhbHBoYT0wLjUpICsgCiAgc2NhbGVfY29sb3JfZ3JhZGllbnQoJzAnLCBsb3cgPSAiI0ZGRkZGRjAwIiwgaGlnaCA9ICIjNmRjMGE2IikgKwogIG5ld19zY2FsZSgiY29sb3IiKSArCiAgICBnZW9tX3BvaW50KGRhdGEgPSBlbWJlZGRpbmdbZW1iZWRkaW5nJGAxYD4wLjEsXSwgCiAgICAgICAgICAgICBhZXMoeCA9IFVNQVBfMSwgeSA9IFVNQVBfMiwgY29sb3IgPSBgMWApLHNoYXBlPTE2LCBzaXplID0gMiwgYWxwaGE9MC41KSArIAogIHNjYWxlX2NvbG9yX2dyYWRpZW50KCcxJywgbG93ID0gIiNGRkZGRkYwMCIsIGhpZ2ggPSAiI2UyYjM5OCIpICsKICAgbmV3X3NjYWxlKCJjb2xvciIpICsKICAgIGdlb21fcG9pbnQoZGF0YSA9IGVtYmVkZGluZ1tlbWJlZGRpbmckYDJgPjAuMSxdLCAKICAgICAgICAgICAgIGFlcyh4ID0gVU1BUF8xLCB5ID0gVU1BUF8yLCBjb2xvciA9IGAyYCksc2hhcGU9MTYsIHNpemUgPSAyLCBhbHBoYT0wLjUpICsgCiAgc2NhbGVfY29sb3JfZ3JhZGllbnQoJzInLCBsb3cgPSAiI0ZGRkZGRjAwIiwgaGlnaCA9ICIjZTJhMmNhIikgKwogICAgIG5ld19zY2FsZSgiY29sb3IiKSArCiAgICBnZW9tX3BvaW50KGRhdGEgPSBlbWJlZGRpbmdbZW1iZWRkaW5nJGAzYD4wLjEsXSwgCiAgICAgICAgICAgICBhZXMoeCA9IFVNQVBfMSwgeSA9IFVNQVBfMiwgY29sb3IgPSBgM2ApLHNoYXBlPTE2LCBzaXplID0gMiwgYWxwaGE9MC41KSArIAogIHNjYWxlX2NvbG9yX2dyYWRpZW50KCczJywgbG93ID0gIiNGRkZGRkYwMCIsIGhpZ2ggPSAiI2QxZWJhOCIpICsKICAgICBuZXdfc2NhbGUoImNvbG9yIikgKwogICAgZ2VvbV9wb2ludChkYXRhID0gZW1iZWRkaW5nW2VtYmVkZGluZyRgNGA+MC4xLF0sIAogICAgICAgICAgICAgYWVzKHggPSBVTUFQXzEsIHkgPSBVTUFQXzIsIGNvbG9yID0gYDRgKSxzaGFwZT0xNiwgc2l6ZSA9IDIsIGFscGhhPTAuNSkgKyAKICBzY2FsZV9jb2xvcl9ncmFkaWVudCgnNCcsIGxvdyA9ICIjRkZGRkZGMDAiLCBoaWdoID0gIiNiMWQ2ZmIiKSArCiAgICAgbmV3X3NjYWxlKCJjb2xvciIpICsKICAgIGdlb21fcG9pbnQoZGF0YSA9IGVtYmVkZGluZ1tlbWJlZGRpbmckYDVgPjAuMSxdLCAKICAgICAgICAgICAgIGFlcyh4ID0gVU1BUF8xLCB5ID0gVU1BUF8yLCBjb2xvciA9IGA1YCksc2hhcGU9MTYsIHNpemUgPSAyLCBhbHBoYT0wLjUpICsgCiAgc2NhbGVfY29sb3JfZ3JhZGllbnQoJzUnLCBsb3cgPSAiI0ZGRkZGRjAwIiwgaGlnaCA9ICIjZmQ5OTk5IikgKwogICAgICAgIHhsYWIoIlVNQVAgMSIpICsgeWxhYigiVU1BUCAyIikgICsKICAgICAgICB0aGVtZShheGlzLmxpbmUgPSBlbGVtZW50X2xpbmUoYXJyb3cgPSBhcnJvdyhsZW5ndGggPSB1bml0KDAuMiwgImNtIikpKSkgKwogICAgICAgIHNjYWxlX3lfY29udGludW91cyhicmVha3MgPSBOVUxMKSArCiAgICAgICAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IE5VTEwpICsgCiAgdGhlbWUocGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwgcGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKSwgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpCmdnc2F2ZSgiZHMwdG9kczF1bWFwLnN2ZyIsZGV2aWNlID0gc3ZnLHBsb3QgPSBnZ29iaixoZWlnaHQgPSA4LHdpZHRoID0gOCkKYGBgCgoKIyMgQVJJIOWSjOiBmuexu+aVsOeahOWFs+ezuwpgYGB7cn0KbHltX2RzMiA8LSByZWFkUkRTKCIuL2x5bV9kczIucmRzIikKCklkZW50cyhseW1fZHMyKSA8LSBseW1fZHMyJGNvbmRpdGlvbnMKbHltX2RzMl9BQyA8LSBzdWJzZXQobHltX2RzMiwgaWRlbnRzID0gIkFDIikKbHltX2RzMl9QQSA8LSBzdWJzZXQobHltX2RzMiwgaWRlbnRzID0gIlBBIikKCnNldC5zZWVkKDcpCmx5bV9QQV9kYXRhIDwtIGdldF9kYXRhX3RhYmxlKGx5bV9kczJfUEEsIGhpZ2h2YXIgPSBULCB0eXBlID0gImRhdGEiKQpseW1fQUNfZGF0YSA8LSBnZXRfZGF0YV90YWJsZShseW1fZHMyX0FDLCBoaWdodmFyID0gVCwgdHlwZSA9ICJkYXRhIikKcmVzIDwtIGxpc3QoKQoKYGBgCgpgYGB7cn0KZm9yKHJlc28gaW4gc2VxKDAuMDUsMC4zLDAuMDUpKQp7CiAgbHltX2RzMl9BQyA8LSBseW1fZHMyX0FDICU+JSBGaW5kTmVpZ2hib3JzKGRpbXMgPSAxOjIwKSAlPiUgRmluZENsdXN0ZXJzKHJlc29sdXRpb24gPSByZXNvKQogIGx5bV9kczJfUEEgPC0gbHltX2RzMl9QQSAlPiUgRmluZE5laWdoYm9ycyhkaW1zID0gMToyMCkgJT4lIEZpbmRDbHVzdGVycyhyZXNvbHV0aW9uID0gcmVzbykKCiAgbHltX1BBX2xhYmVsIDwtIGFzLm51bWVyaWMoYXMuY2hhcmFjdGVyKElkZW50cyhseW1fZHMyX1BBKSkpCgogIGluZGV4IDwtIGMoMTpkaW0obHltX1BBX2RhdGEpWzJdKSAlPiUgc2FtcGxlKGNlaWxpbmcoMC4zKmRpbShseW1fUEFfZGF0YSlbMl0pLCByZXBsYWNlID0gRiwgcHJvYiA9IE5VTEwpCiAgY29sbmFtZXMobHltX1BBX2RhdGEpIDwtIE5VTEwKICBseW1fUEFfdHJhaW5fZGF0YSA8LSBsaXN0KGRhdGEgPSB0KGFzKGx5bV9QQV9kYXRhWywtaW5kZXhdLCJkZ0NNYXRyaXgiKSksIGxhYmVsID0gbHltX1BBX2xhYmVsWy1pbmRleF0pCiAgbHltX1BBX3Rlc3RfZGF0YSA8LSBsaXN0KGRhdGEgPSB0KGFzKGx5bV9QQV9kYXRhWyxpbmRleF0sImRnQ01hdHJpeCIpKSwgbGFiZWwgPSBseW1fUEFfbGFiZWxbaW5kZXhdKQogIAogIGx5bV9QQV90cmFpbiA8LSB4Z2IuRE1hdHJpeChkYXRhID0gbHltX1BBX3RyYWluX2RhdGEkZGF0YSxsYWJlbCA9IGx5bV9QQV90cmFpbl9kYXRhJGxhYmVsKQogIGx5bV9QQV90ZXN0IDwtIHhnYi5ETWF0cml4KGRhdGEgPSBseW1fUEFfdGVzdF9kYXRhJGRhdGEsbGFiZWwgPSBseW1fUEFfdGVzdF9kYXRhJGxhYmVsKQogIAogIHdhdGNobGlzdCA8LSBsaXN0KHRyYWluID0gbHltX1BBX3RyYWluLCBldmFsID0gbHltX1BBX3Rlc3QpCiAgeGdiX3BhcmFtIDwtIGxpc3QoZXRhID0gMC4yLCBtYXhfZGVwdGggPSA2LCAKICAgICAgICAgICAgICAgICAgICBzdWJzYW1wbGUgPSAwLjYsICBudW1fY2xhc3MgPSBsZW5ndGgodGFibGUoSWRlbnRzKGx5bV9kczJfUEEpKSksCiAgICAgICAgICAgICAgICAgICAgb2JqZWN0aXZlID0gIm11bHRpOnNvZnRtYXgiLCBldmFsX21ldHJpYyA9ICdtbG9nbG9zcycpCiAgCiAgYnN0X21vZGVsIDwtIHhnYi50cmFpbih4Z2JfcGFyYW0sIGx5bV9QQV90cmFpbiwgbnJvdW5kcyA9IDEwMCwgd2F0Y2hsaXN0LCB2ZXJib3NlID0gMCkKICAKICAKICBseW1fQUNfbGFiZWwgPC0gYXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIoSWRlbnRzKGx5bV9kczJfQUMpKSkKICBjb2xuYW1lcyhseW1fQUNfZGF0YSkgPC0gTlVMTAogIGx5bV9BQ190ZXN0X2RhdGEgPC0gbGlzdChkYXRhID0gdChhcyhseW1fQUNfZGF0YSwiZGdDTWF0cml4IikpLCBsYWJlbCA9IGx5bV9BQ19sYWJlbCkKICBseW1fQUNfdGVzdCA8LSB4Z2IuRE1hdHJpeChkYXRhID0gbHltX0FDX3Rlc3RfZGF0YSRkYXRhLGxhYmVsID0gbHltX0FDX3Rlc3RfZGF0YSRsYWJlbCkKICBwcmVkaWN0X2x5bV9BQ190ZXN0IDwtIHJvdW5kKHByZWRpY3QoYnN0X21vZGVsLCBuZXdkYXRhID0gbHltX0FDX3Rlc3QpKQogIAogIHJlcyA8LSBhcHBlbmQoeCA9IHJlcyx2YWx1ZXMgPSBhZGp1c3RlZFJhbmRJbmRleChwcmVkaWN0X2x5bV9BQ190ZXN0LCBseW1fQUNfdGVzdF9kYXRhJGxhYmVsKSkKfQpyZXMKYGBgCgojIyBBUkkg5ZKM6IGa57G75pWw55qE5YWz57O7CmBgYHtyfQogZHMyIDwtIHJlYWRSRFMoIi4vZHMyLnJkcyIpCgpJZGVudHMoIGRzMikgPC0gIGRzMiRjb25kaXRpb25zCiBkczJfQUMgPC0gc3Vic2V0KCBkczIsIGlkZW50cyA9ICJBQyIpCiBkczJfUEEgPC0gc3Vic2V0KCBkczIsIGlkZW50cyA9ICJQQSIpCgpzZXQuc2VlZCg3KQogUEFfZGF0YSA8LSBnZXRfZGF0YV90YWJsZSggZHMyX1BBLCBoaWdodmFyID0gVCwgdHlwZSA9ICJkYXRhIikKIEFDX2RhdGEgPC0gZ2V0X2RhdGFfdGFibGUoIGRzMl9BQywgaGlnaHZhciA9IFQsIHR5cGUgPSAiZGF0YSIpCmRzMl9yZXMgPC0gbGlzdCgpCgpgYGAKCmBgYHtyfQpmb3IocmVzbyBpbiBzZXEoMC4wNSwwLjMsMC4wNSkpCnsKICAgZHMyX0FDIDwtICBkczJfQUMgJT4lIEZpbmROZWlnaGJvcnMoZGltcyA9IDE6MjApICU+JSBGaW5kQ2x1c3RlcnMocmVzb2x1dGlvbiA9IHJlc28pCiAgIGRzMl9QQSA8LSAgZHMyX1BBICU+JSBGaW5kTmVpZ2hib3JzKGRpbXMgPSAxOjIwKSAlPiUgRmluZENsdXN0ZXJzKHJlc29sdXRpb24gPSByZXNvKQoKICAgUEFfbGFiZWwgPC0gYXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIoSWRlbnRzKCBkczJfUEEpKSkKCiAgaW5kZXggPC0gYygxOmRpbSggUEFfZGF0YSlbMl0pICU+JSBzYW1wbGUoY2VpbGluZygwLjMqZGltKCBQQV9kYXRhKVsyXSksIHJlcGxhY2UgPSBGLCBwcm9iID0gTlVMTCkKICBjb2xuYW1lcyggUEFfZGF0YSkgPC0gTlVMTAogICBQQV90cmFpbl9kYXRhIDwtIGxpc3QoZGF0YSA9IHQoYXMoIFBBX2RhdGFbLC1pbmRleF0sImRnQ01hdHJpeCIpKSwgbGFiZWwgPSAgUEFfbGFiZWxbLWluZGV4XSkKICAgUEFfdGVzdF9kYXRhIDwtIGxpc3QoZGF0YSA9IHQoYXMoIFBBX2RhdGFbLGluZGV4XSwiZGdDTWF0cml4IikpLCBsYWJlbCA9ICBQQV9sYWJlbFtpbmRleF0pCiAgCiAgIFBBX3RyYWluIDwtIHhnYi5ETWF0cml4KGRhdGEgPSAgUEFfdHJhaW5fZGF0YSRkYXRhLGxhYmVsID0gIFBBX3RyYWluX2RhdGEkbGFiZWwpCiAgIFBBX3Rlc3QgPC0geGdiLkRNYXRyaXgoZGF0YSA9ICBQQV90ZXN0X2RhdGEkZGF0YSxsYWJlbCA9ICBQQV90ZXN0X2RhdGEkbGFiZWwpCiAgCiAgd2F0Y2hsaXN0IDwtIGxpc3QodHJhaW4gPSAgUEFfdHJhaW4sIGV2YWwgPSAgUEFfdGVzdCkKICB4Z2JfcGFyYW0gPC0gbGlzdChldGEgPSAwLjIsIG1heF9kZXB0aCA9IDYsIAogICAgICAgICAgICAgICAgICAgIHN1YnNhbXBsZSA9IDAuNiwgIG51bV9jbGFzcyA9IGxlbmd0aCh0YWJsZShJZGVudHMoIGRzMl9QQSkpKSwKICAgICAgICAgICAgICAgICAgICBvYmplY3RpdmUgPSAibXVsdGk6c29mdG1heCIsIGV2YWxfbWV0cmljID0gJ21sb2dsb3NzJykKICAKICBic3RfbW9kZWwgPC0geGdiLnRyYWluKHhnYl9wYXJhbSwgIFBBX3RyYWluLCBucm91bmRzID0gMTAwLCB3YXRjaGxpc3QsIHZlcmJvc2UgPSAwKQogIAogIAogICBBQ19sYWJlbCA8LSBhcy5udW1lcmljKGFzLmNoYXJhY3RlcihJZGVudHMoIGRzMl9BQykpKQogIGNvbG5hbWVzKCBBQ19kYXRhKSA8LSBOVUxMCiAgIEFDX3Rlc3RfZGF0YSA8LSBsaXN0KGRhdGEgPSB0KGFzKCBBQ19kYXRhLCJkZ0NNYXRyaXgiKSksIGxhYmVsID0gIEFDX2xhYmVsKQogICBBQ190ZXN0IDwtIHhnYi5ETWF0cml4KGRhdGEgPSAgQUNfdGVzdF9kYXRhJGRhdGEsbGFiZWwgPSAgQUNfdGVzdF9kYXRhJGxhYmVsKQogIHByZWRpY3RfQUNfdGVzdCA8LSByb3VuZChwcmVkaWN0KGJzdF9tb2RlbCwgbmV3ZGF0YSA9IEFDX3Rlc3QpKQogIAogIGRzMl9yZXMgPC0gYXBwZW5kKHggPSBkczJfcmVzLHZhbHVlcyA9IGFkanVzdGVkUmFuZEluZGV4KHByZWRpY3RfQUNfdGVzdCwgIEFDX3Rlc3RfZGF0YSRsYWJlbCkpCn0KZHMyX3JlcwoKYGBgCgoKIyMjIOe7mOWbvgpgYGB7ciBmaWcud2lkdGg9NCwgZmlnLmhlaWdodD00fQpkYXRhIDwtIGRhdGEuZnJhbWUocmVzb2x1dGlvbiA9IHNlcSgwLjA1LDAuMywwLjA1KSwgbHltX0FSSSA9IGFzLm51bWVyaWMocmVzKSwgU01DX0FSSSA9IGFzLm51bWVyaWMoZHMyX3JlcykpCiAjIGdncGxvdChkYXRhLCBhZXMoeD1yZXNvbHV0aW9uLCB5PUFSSSkpICsKICMgICAgZ2VvbV9saW5lKGNvbG9yPSIjZTJhMmNhNTAiLCBzaXplPTIpICsKICMgICAgZ2VvbV9wb2ludChzaGFwZT0yMSwgY29sb3I9IiNlMmEyY2EiLCBmaWxsPSIjZTJhMmNhIiwgc2l6ZT0zKSArCiAjICAgIHRoZW1lX2NsYXNzaWMoKSArCiAjICAgIGdndGl0bGUoInJlc29sdXRpb24tQVJJIikgKyB4bGltKDAuMSwxKSAreWxpbSgwLDEpCndyaXRlLmNzdihkYXRhLCIuL2RhdGF0YWJsZS9yZXNvLUFSSS5jc3YiKQpwbG90X2x5KGRhdGEsIHggPSB+cmVzb2x1dGlvbikgJT4lIAogIGFkZF90cmFjZSh5ID0gfmx5bV9BUkksIG5hbWUgPSAnbHltJywgbW9kZSA9ICdsaW5lcyttYXJrZXJzJyxzaXplID0gNCwgbWFya2VycyA9IGxpc3QoY29sb3IgPSAiIzE1OGFmZiIpLAogICAgICAgICAgICBsaW5lID0gbGlzdChjb2xvciA9ICcjYjFkNmZiJywgd2lkdGggPSA0KSwgdHlwZSA9ICdzY2F0dGVyJykgJT4lCiAgYWRkX3RyYWNlKHkgPSB+U01DX0FSSSwgbmFtZSA9ICdTTUMnLCBtb2RlID0gJ2xpbmVzK21hcmtlcnMnLHNpemUgPSA0LCBtYXJrZXJzID0gbGlzdChjb2xvciA9ICIjZmYyMTIxIiksIAogICAgICAgICAgICBsaW5lID0gbGlzdChjb2xvciA9ICcjZTJhMmNhJywgd2lkdGggPSA0KSwgdHlwZSA9ICdzY2F0dGVyJyklPiUKICBsYXlvdXQoeGF4aXMgPSBsaXN0KHplcm9saW5lY29sb3IgPSAnI2ZmZmZmZicsIHplcm9saW5ld2lkdGggPSAyLCByYW5nZSA9IGMoMCwgMC4zMiksCiAgICAgICAgICAgICAgICAgICAgICAgZ3JpZGNvbG9yID0gJ2ZmZmYnLCBkdGljaz0wLjA1LCB0aXRsZSA9ICJyZXNvbHV0aW9uIiksCiAgICAgICAgIHlheGlzID0gbGlzdCh6ZXJvbGluZWNvbG9yID0gJyNmZmZmZmYnLCB6ZXJvbGluZXdpZHRoID0gMiwgcmFuZ2UgPSBjKDAuMiwgMC45KSwKICAgICAgICAgICAgICAgICAgICAgICBncmlkY29sb3IgPSAnZmZmZicsIGR0aWNrPTAuMSwgIHRpdGxlID0gIkFSSSIpKSAlPiUKICBsYXlvdXQoZm9udCA9IGxpc3QoZmFtaWx5ID0gIkFyaWFsIiwgc2l6ZSA9IDIwLCBjb2xvciA9ICJibGFjayIpKQpgYGAKCgoKIyDop6Pph4rmgKcKYGBge3J9CmxpYnJhcnkoREFMRVgpCmxpYnJhcnkobW9kZWxTdHVkaW8pCnNvdXJjZSgidGlhbmZlbmdSd3JhcHBlcnMuUiIpCmRzMiA8LSByZWFkUkRTKCJkczIucmRzIikKSWRlbnRzKGRzMikgPC0gZHMyJENsYXNzaWZpY2F0aW9uMQpkczIgPC0gUmVuYW1lSWRlbnRzKGRzMiwgJ1NNQzEnID0gMCwgJ0ZpYnJvbXlvY3l0ZScgPSAxLCAnUGVyaWN5dGUnID0gMiwgJ0ZpYnJvYmxhc3QnID0gMywgJ1NNQzInID0gNCkKZHMyX2RhdGEgPC0gZ2V0X2RhdGFfdGFibGUoZHMyLCBoaWdodmFyID0gVCwgdHlwZSA9ICJkYXRhIikKZHMyX2xhYmVsIDwtIGFzLm51bWVyaWMoYXMuY2hhcmFjdGVyKElkZW50cyhkczIpKSkKaW5kZXggPC0gYygxOmRpbShkczJfZGF0YSlbMl0pICU+JSBzYW1wbGUoY2VpbGluZygwLjMqZGltKGRzMl9kYXRhKVsyXSksIHJlcGxhY2UgPSBGLCBwcm9iID0gTlVMTCkKIyBjb2xuYW1lcyhkczJfZGF0YSkgPC0gTlVMTApkczJfdHJhaW5fZGF0YSA8LSBsaXN0KGRhdGEgPSB0KGFzKGRzMl9kYXRhWywtaW5kZXhdLCJkZ0NNYXRyaXgiKSksIGxhYmVsID0gZHMyX2xhYmVsWy1pbmRleF0pCmRzMl90ZXN0X2RhdGEgPC0gbGlzdChkYXRhID0gdChhcyhkczJfZGF0YVssaW5kZXhdLCJkZ0NNYXRyaXgiKSksIGxhYmVsID0gZHMyX2xhYmVsW2luZGV4XSkKZHMyX3RyYWluIDwtIHhnYi5ETWF0cml4KGRhdGEgPSBkczJfdHJhaW5fZGF0YSRkYXRhLGxhYmVsID0gZHMyX3RyYWluX2RhdGEkbGFiZWwpCmRzMl90ZXN0IDwtIHhnYi5ETWF0cml4KGRhdGEgPSBkczJfdGVzdF9kYXRhJGRhdGEsbGFiZWwgPSBkczJfdGVzdF9kYXRhJGxhYmVsKQpgYGAKCgpgYGB7cn0Kd2F0Y2hsaXN0IDwtIGxpc3QodHJhaW4gPSBkczJfdHJhaW4sIGV2YWwgPSBkczJfdGVzdCkKeGdiX3BhcmFtIDwtIGxpc3QoZXRhID0gMC4yLCBtYXhfZGVwdGggPSA2LCAKICAgICAgICAgICAgICAgICAgc3Vic2FtcGxlID0gMC42LCAgbnVtX2NsYXNzID0gbGVuZ3RoKHRhYmxlKElkZW50cyhkczIpKSksCiAgICAgICAgICAgICAgICAgIG9iamVjdGl2ZSA9ICJtdWx0aTpzb2Z0cHJvYiIsIGV2YWxfbWV0cmljID0gJ21sb2dsb3NzJykKYnN0X21vZGVsIDwtIHhnYi50cmFpbih4Z2JfcGFyYW0sIGRzMl90cmFpbiwgbnJvdW5kcyA9IDEwMCwgd2F0Y2hsaXN0LCB2ZXJib3NlID0gMCkKCiMgc2F2ZVJEUyhic3RfbW9kZWwsInJlZHVjZWRfZHMybW9kZWwucmRzIikKCmBgYAoKCmBgYHtyfQojIOW7uueri+ino+mHiuWZqApleHBsYWluZXJfeGdiIDwtIERBTEVYOjpleHBsYWluKGJzdF9tb2RlbCwgZGF0YSA9IGRzMl90cmFpbl9kYXRhJGRhdGFbMToyMDAsXSwgeSA9IGRzMl90cmFpbl9kYXRhJGxhYmVsWzE6MjAwXSwgbGFiZWwgPSAiWEdCb29zdCIpCgojIG1vZGVsU3R1ZGlvOjptb2RlbFN0dWRpbyhleHBsYWluZXIgPSBleHBsYWluZXJfeGdiLCBuZXdfb2JzZXJ2YXRpb24gPSBkczJfdHJhaW5fZGF0YSRkYXRhWzE6MixdICkKcDEgPC0gREFMRVg6OnZhcmlhYmxlX3Byb2ZpbGUoZXhwbGFpbmVyX3hnYiwgdmFyaWFibGUgPSAiQUNLUjEiLCB0eXBlID0gImFjY3VtdWxhdGVkIikKcGxvdChwMSkKcDIgPC0gc2luZ2xlX3ZhcmlhYmxlKGV4cGxhaW5lcl94Z2IsIHJvd25hbWVzKGRzMl9kYXRhKVsxOjEwXSwgdHlwZSA9ICJwZHAiKQpwbG90KHAyKQojIHAzIDwtIHZhcmlhYmxlX2ltcG9ydGFuY2UoZXhwbGFpbmVyX3hnYiwgdmFyaWFibGUgPXJvd25hbWVzKGRzMl9kYXRhKVsxOjEwXSxsb3NzX2Z1bmN0aW9uID0gbG9zc19yb290X21lYW5fc3F1YXJlKQpgYGAKCmBgYHtyfQpyZWR1Y2VkX2RzMm1vZGVsIDwtIHJlYWRSRFMoInJlZHVjZWRfZHMybW9kZWwucmRzIikKcDEgPC0geGdiLnBsb3QudHJlZShmZWF0dXJlX25hbWVzID0gcm93bmFtZXMoZHMyX2RhdGEpLCBtb2RlbCA9IHJlZHVjZWRfZHMybW9kZWwscmVuZGVyID0gRikKRGlhZ3JhbW1lUjo6cmVuZGVyX2dyYXBoKHAxKQpzYXZlUkRTKHAxLCJkczJfdHJlZS5yZHMiKQpgYGAKCmBgYHtyfQpwMiA8LSB4Z2IuZ2dwbG90LnNoYXAuc3VtbWFyeShkYXRhID0gZHMyX3RyYWluX2RhdGEkZGF0YSwgZmVhdHVyZXMgPSByb3duYW1lcyhkczJfZGF0YSlbMToxMF0sIG1vZGVsID0gcmVkdWNlZF9kczJtb2RlbCkKCnhnYi5nZ3Bsb3QuZGVlcG5lc3MobW9kZWwgPSByZWR1Y2VkX2RzMm1vZGVsLCB3aGljaCA9IGMoIjJ4MSIsICJtYXguZGVwdGgiLCAibWVkLmRlcHRoIiwgIm1lZC53ZWlnaHQiKSkKCnNoYXB2YWx1ZXMgPC0geGdiLnBsb3Quc2hhcChkYXRhID0gZHMyX3RyYWluX2RhdGEkZGF0YSwgZmVhdHVyZXMgPSByb3duYW1lcyhkczJfZGF0YSlbMToxMF0sIG1vZGVsID0gcmVkdWNlZF9kczJtb2RlbCxwbG90ID0gRikKYGBgCgpgYGB7cn0KbGlicmFyeShTSEFQZm9yeGdib29zdCkKYGBgCgoKYGBge3J9CnhnYl9wYXJhbSA8LSBsaXN0KGV0YSA9IDAuMiwgbWF4X2RlcHRoID0gNiwgCiAgICAgICAgICAgICAgICAgIHN1YnNhbXBsZSA9IDAuNiwgIG51bV9jbGFzcyA9IGxlbmd0aCh0YWJsZShJZGVudHMoZHMyKSkpLAogICAgICAgICAgICAgICAgICBvYmplY3RpdmUgPSAibXVsdGk6c29mdHByb2IiLCBldmFsX21ldHJpYyA9ICdtbG9nbG9zcycpCgpyZWR1Y2VkX2RzMm1vZGVsIDwtIHhnYm9vc3Q6OnhnYm9vc3QoCiAgZGF0YSA9IGRzMl90ZXN0X2RhdGEkZGF0YSwgbGFiZWwgPSBkczJfdGVzdF9kYXRhJGxhYmVsLAogcGFyYW1zID0geGdiX3BhcmFtLCBucm91bmRzID0gMTAwLCB2ZXJib3NlID0gRkFMU0UpCgoKc2hhcF9jb250cmliIDwtIHByZWRpY3QocmVkdWNlZF9kczJtb2RlbCwgZHMyX3RyYWluX2RhdGEkZGF0YSwgcHJlZGNvbnRyaWIgPSBUUlVFKQpzaGFwX2NvbnRyaWIgPC0gZGF0YS5mcmFtZShzaGFwX2NvbnRyaWJbWzJdXSkgIyPmj5Dlj5ZmYm3lr7nlupTnmoTliIbnsbsKQklBUzAgPC0gc2hhcF9jb250cmliWywgbmNvbChzaGFwX2NvbnRyaWIpXVsxXQpzaGFwX2NvbnRyaWJbLCJCSUFTIl0gPC0gTlVMTAppbXAgPC0gY29sTWVhbnMoYWJzKHNoYXBfY29udHJpYikpCm1lYW5fc2hhcF9zY29yZSA8LSBpbXBbb3JkZXIoaW1wLCBkZWNyZWFzaW5nID0gVCldCgpzaGFwX3ZhbHVlczwtIGxpc3Qoc2hhcF9zY29yZSA9IHNoYXBfY29udHJpYiwgbWVhbl9zaGFwX3Njb3JlID0gbWVhbl9zaGFwX3Njb3JlLCAKICAgIEJJQVMwID0gQklBUzApCgojIFRoZSByYW5rZWQgZmVhdHVyZXMgYnkgbWVhbiB8U0hBUHwKc2hhcF92YWx1ZXMkbWVhbl9zaGFwX3Njb3JlCgojIFRvIHByZXBhcmUgdGhlIGxvbmctZm9ybWF0IGRhdGE6CnNoYXBfbG9uZyA8LSBzaGFwLnByZXAoc2hhcF9jb250cmliID0gc2hhcF9jb250cmliLCBYX3RyYWluID0gZHMyX3RyYWluX2RhdGEkZGF0YSwgdG9wX24gPSAzMCkKCiMgc29tZXRpbWVzIGZvciBhIHByZXZpZXcsIHlvdSB3YW50IHRvIHBsb3QgbGVzcyBkYXRhIHRvIG1ha2UgaXQgZmFzdGVyIHVzaW5nIGBkaWx1dGVgCnNoYXAucGxvdC5zdW1tYXJ5KHNoYXBfbG9uZywgeF9ib3VuZCAgPSAxLjIsIGRpbHV0ZSA9IDMwKQpgYGAKCgpgYGB7cn0Kc2hhcF9jb250cmliIDwtIHByZWRpY3QocmVkdWNlZF9kczJtb2RlbCwgZHMyX3RyYWluX2RhdGEkZGF0YSwgcHJlZGNvbnRyaWIgPSBUUlVFKQpzaGFwX2NvbnRyaWIgPC0gZGF0YS5mcmFtZShzaGFwX2NvbnRyaWJbWzRdXSkgIyPmj5Dlj5ZTTUMx5a+55bqU55qE5YiG57G7CkJJQVMwIDwtIHNoYXBfY29udHJpYlssIG5jb2woc2hhcF9jb250cmliKV1bMV0Kc2hhcF9jb250cmliWywiQklBUyJdIDwtIE5VTEwKaW1wIDwtIGNvbE1lYW5zKGFicyhzaGFwX2NvbnRyaWIpKQptZWFuX3NoYXBfc2NvcmUgPC0gaW1wW29yZGVyKGltcCwgZGVjcmVhc2luZyA9IFQpXQoKc2hhcF92YWx1ZXM8LSBsaXN0KHNoYXBfc2NvcmUgPSBzaGFwX2NvbnRyaWIsIG1lYW5fc2hhcF9zY29yZSA9IG1lYW5fc2hhcF9zY29yZSwgCiAgICBCSUFTMCA9IEJJQVMwKQoKIyBUbyBwcmVwYXJlIHRoZSBsb25nLWZvcm1hdCBkYXRhOgpzaGFwX2xvbmcgPC0gc2hhcC5wcmVwKHNoYXBfY29udHJpYiA9IHNoYXBfY29udHJpYiwgWF90cmFpbiA9IGRzMl90cmFpbl9kYXRhJGRhdGEsdG9wX24gPSAzMCkKCnNoYXAucGxvdC5zdW1tYXJ5KHNoYXBfbG9uZywgeF9ib3VuZCAgPSAxLjIsIGRpbHV0ZSA9IDMwKQpgYGAKCmBgYHtyfQpwbG90X2RhdGEgPC0gc2hhcC5wcmVwLnN0YWNrLmRhdGEoc2hhcF9jb250cmliID0gc2hhcF92YWx1ZXMkc2hhcF9zY29yZSwgdG9wX24gPSA1LCBuX2dyb3VwcyA9IDIpCnNoYXAucGxvdC5mb3JjZV9wbG90KHBsb3RfZGF0YSwgem9vbV9pbl9sb2NhdGlvbiA9IDUwMCwgeV9wYXJlbnRfbGltaXQgPSBjKC0xLDEpKQpzaGFwLnBsb3QuZm9yY2VfcGxvdF9ieWdyb3VwKHBsb3RfZGF0YSkKc2hhcC5wbG90LmZvcmNlX3Bsb3QocGxvdF9kYXRhLHpvb21faW5fbG9jYXRpb24gPSA4MDApCmBgYApgYGB7cn0KZGYgPC0gRmV0Y2hEYXRhKGRzMix2YXJzID0gYygiQkdOIiwiTFVNIiwiRENOIiwiQUNUQTIiKSkKZGYgPC0gY2JpbmQoZGYsIGNsdXN0ZXIgPSBkczIkQ2xhc3NpZmljYXRpb24xKQoKc2hhcC5wbG90LmRlcGVuZGVuY2UoZGF0YV9sb25nID0gc2hhcF9sb25nLCB4PSAiTFVNIiwgY29sb3JfZmVhdHVyZSA9ICJMVU0iKSsgbXl0aGVtZSArIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsb2VzcyIpICsgc2NhbGVfeF9jb250aW51b3VzKGxpbWl0cyA9IGMoMCw0LjUpKSArIHNjYWxlX2NvbG91cl9ncmFkaWVudChsb3c9IiMxRTkwRkYiLCBoaWdoPSIjZmYyMTIxIikKCnNoYXAucGxvdC5kZXBlbmRlbmNlKGRhdGFfbG9uZyA9IHNoYXBfbG9uZywgeD0gIkRDTiIsIGNvbG9yX2ZlYXR1cmUgPSAiRENOIikrIG15dGhlbWUgKyBnZW9tX3Ntb290aChtZXRob2QgPSAibG9lc3MiKSsgc2NhbGVfY29sb3VyX2dyYWRpZW50KGxvdz0iIzFFOTBGRiIsIGhpZ2g9IiNmZjIxMjEiKQoKc2hhcC5wbG90LmRlcGVuZGVuY2UoZGF0YV9sb25nID0gc2hhcF9sb25nLCB4PSAiQUNUQTIiLCBjb2xvcl9mZWF0dXJlID0gIkFDVEEyIikgKyBteXRoZW1lICsgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxvZXNzIikrIHNjYWxlX2NvbG91cl9ncmFkaWVudChsb3c9IiMxRTkwRkYiLCBoaWdoPSIjZmYyMTIxIikKCnNoYXAucGxvdC5kZXBlbmRlbmNlKGRhdGFfbG9uZyA9IHNoYXBfbG9uZywgeD0gIkNGSCIsIGNvbG9yX2ZlYXR1cmUgPSAiQ0ZIIikgKyBteXRoZW1lICsgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxvZXNzIikrIHNjYWxlX2NvbG91cl9ncmFkaWVudChsb3c9IiMxRTkwRkYiLCBoaWdoPSIjZmYyMTIxIikgK3NjYWxlX3hfY29udGludW91cyhsaW1pdHMgPSBjKDAsNCkpCmBgYAoKCmBgYHtyfQpzaGFwX2ludCA8LSBwcmVkaWN0KHJlZHVjZWRfZHMybW9kZWwsIGRzMl90cmFpbl9kYXRhJGRhdGFbMToyMCxdLCBwcmVkaW50ZXJhY3Rpb24gPSBUUlVFKQoKc2hhcF9jb250cmliIDwtIHByZWRpY3QocmVkdWNlZF9kczJtb2RlbCwgZHMyX3RyYWluX2RhdGEkZGF0YVsxOjIwLF0sIHByZWRjb250cmliID0gVFJVRSkKc2hhcF9jb250cmliIDwtIGRhdGEuZnJhbWUoc2hhcF9jb250cmliW1syXV0pICMj5o+Q5Y+WRkJN5a+55bqU55qE5YiG57G7CkJJQVMwIDwtIHNoYXBfY29udHJpYlssIG5jb2woc2hhcF9jb250cmliKV1bMV0Kc2hhcF9jb250cmliWywiQklBUyJdIDwtIE5VTEwKaW1wIDwtIGNvbE1lYW5zKGFicyhzaGFwX2NvbnRyaWIpKQptZWFuX3NoYXBfc2NvcmUgPC0gaW1wW29yZGVyKGltcCwgZGVjcmVhc2luZyA9IFQpXQoKc2hhcF92YWx1ZXM8LSBsaXN0KHNoYXBfc2NvcmUgPSBzaGFwX2NvbnRyaWIsIG1lYW5fc2hhcF9zY29yZSA9IG1lYW5fc2hhcF9zY29yZSwgQklBUzAgPSBCSUFTMCkKc2hhcF9sb25nIDwtIHNoYXAucHJlcChzaGFwX2NvbnRyaWIgPSBzaGFwX2NvbnRyaWIsIFhfdHJhaW4gPSBkczJfdHJhaW5fZGF0YSRkYXRhWzE6MjAsXSx0b3BfbiA9IDEwKQoKc2hhcC5wbG90LmRlcGVuZGVuY2UoZGF0YV9sb25nID0gc2hhcF9sb25nLCBkYXRhX2ludCA9IHNoYXBfaW50W1syXV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHg9ICJMVU0iLCB5ID0gIkxVTSIsIGNvbG9yX2ZlYXR1cmUgPSAiTFVNIikKYGBgCgoKYGBge3J9CmRhdGEoImlyaXMiKQpYMSA9IGFzLm1hdHJpeChpcmlzWywtNV0pCm1vZDEgPSB4Z2Jvb3N0Ojp4Z2Jvb3N0KAogIGRhdGEgPSBYMSwgbGFiZWwgPSBpcmlzJFNwZWNpZXMsIGdhbW1hID0gMCwgZXRhID0gMSwKICBsYW1iZGEgPSAwLCBucm91bmRzID0gMSwgdmVyYm9zZSA9IEZBTFNFKQoKIyBzaGFwLnZhbHVlcyhtb2RlbCwgWF9kYXRhc2V0KSByZXR1cm5zIHRoZSBTSEFQCiMgZGF0YSBtYXRyaXggYW5kIHJhbmtlZCBmZWF0dXJlcyBieSBtZWFufFNIQVB8CnNoYXBfdmFsdWVzIDwtIHNoYXAudmFsdWVzKHhnYl9tb2RlbCA9IG1vZDEsIFhfdHJhaW4gPSBYMSkKc2hhcF92YWx1ZXMkbWVhbl9zaGFwX3Njb3JlCnNoYXBfdmFsdWVzX2lyaXMgPC0gc2hhcF92YWx1ZXMkc2hhcF9zY29yZQoKCnNoYXBfbG9uZyA8LSBzaGFwLnByZXAoeGdiX21vZGVsID0gbW9kMSwgWF90cmFpbiA9IFgxKQpgYGAKCiMjIGNvbmZpZGVuY2Xmm7Lnur8KYGBge3J9CmJzdF9tb2RlbCA8LSByZWFkUkRTKCIuL3JlZHVjZWRfZHMybW9kZWwucmRzIikKI+mihOa1i+e7k+aenApwcmVkaWN0X2RzMl90ZXN0IDwtIHByZWRpY3QoYnN0X21vZGVsLCBuZXdkYXRhID0gZHMyX3Rlc3QpCgpwcmVkaWN0X3Byb3BfZHMyIDwtIG1hdHJpeChkYXRhPXByZWRpY3RfZHMyX3Rlc3QsIG5yb3cgPSBsZW5ndGgobGV2ZWxzKElkZW50cyhkczIpKSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICBuY29sID0gbnJvdyhkczJfdGVzdF9kYXRhJGRhdGEpLCBieXJvdyA9IEZBTFNFLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgZGltbmFtZXMgPSBsaXN0KGxldmVscyhJZGVudHMoZHMyKSkscm93bmFtZXMoZHMyX3Rlc3RfZGF0YSRkYXRhKSkpCmRmIDwtIGRhdGEuZnJhbWUodChwcmVkaWN0X3Byb3BfZHMyKSkKCmRmIDwtIGFycmFuZ2UoZGYsWDEsYnlfZ3JvdXAgPSBGKQojIyDlvpfliLDliIbnvqTnu5PmnpwKIyBkczJfcmVzIDwtIGFwcGx5KHByZWRpY3RfcHJvcF9kczIsMixmdW5jLHJvd25hbWVzKHByZWRpY3RfcHJvcF9kczIpKQoKCmRmMiA8LSBjYmluZChkZixpbmRleCA9IDE6bnJvdyhkZikpCmdncGxvdChkZjIpK2dlb21fcG9pbnQoYWVzKHg9aW5kZXgseSA9IFgxKSxjb2xvciA9ICIjZTJiMzk4IixhbHBoYSA9IDEpKyBnZW9tX3BvaW50KGFlcyh4PWluZGV4LHk9WDMpLGNvbG9yID0gIiNkMWViYTgiLGFscGhhID0gMSkrIHRoZW1lX2NsYXNzaWMoKSArIG15dGhlbWUgI0ZC5LiORkJN5YiG56a7CgpnZ3Bsb3QoZGYyKStnZW9tX3BvaW50KGFlcyh4PWluZGV4LHkgPSBYMSksY29sb3IgPSAiI2UyYjM5OCIsYWxwaGEgPSAxKSsgZ2VvbV9wb2ludChhZXMoeD1pbmRleCx5PVgwKSxjb2xvciA9ICIjNmRjMGE2IixhbHBoYSA9IDEpKyB0aGVtZV9jbGFzc2ljKCkgKyBteXRoZW1lICNTTUPkuI5GQk0KYGBgCgpBZGQgYSBuZXcgY2h1bmsgYnkgY2xpY2tpbmcgdGhlICpJbnNlcnQgQ2h1bmsqIGJ1dHRvbiBvbiB0aGUgdG9vbGJhciBvciBieSBwcmVzc2luZyAqQ3RybCtBbHQrSSouCgpXaGVuIHlvdSBzYXZlIHRoZSBub3RlYm9vaywgYW4gSFRNTCBmaWxlIGNvbnRhaW5pbmcgdGhlIGNvZGUgYW5kIG91dHB1dCB3aWxsIGJlIHNhdmVkIGFsb25nc2lkZSBpdCAoY2xpY2sgdGhlICpQcmV2aWV3KiBidXR0b24gb3IgcHJlc3MgKkN0cmwrU2hpZnQrSyogdG8gcHJldmlldyB0aGUgSFRNTCBmaWxlKS4KClRoZSBwcmV2aWV3IHNob3dzIHlvdSBhIHJlbmRlcmVkIEhUTUwgY29weSBvZiB0aGUgY29udGVudHMgb2YgdGhlIGVkaXRvci4gQ29uc2VxdWVudGx5LCB1bmxpa2UgKktuaXQqLCAqUHJldmlldyogZG9lcyBub3QgcnVuIGFueSBSIGNvZGUgY2h1bmtzLiBJbnN0ZWFkLCB0aGUgb3V0cHV0IG9mIHRoZSBjaHVuayB3aGVuIGl0IHdhcyBsYXN0IHJ1biBpbiB0aGUgZWRpdG9yIGlzIGRpc3BsYXllZC4=